mirror of
https://github.com/2dust/v2rayNG.git
synced 2026-10-29 14:39:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15b4fff8e4 | ||
|
|
1007f54743 | ||
|
|
5369303e18 | ||
|
|
318afdbafc | ||
|
|
ec04df4693 | ||
|
|
53cccd9089 | ||
|
|
f226ace30d | ||
|
|
10a4221b25 | ||
|
|
ffac1476c0 | ||
|
|
7a0472b63d | ||
|
|
62cecc4d0d | ||
|
|
eb56b2e89e | ||
|
|
e83dba94a0 | ||
|
|
42c12fdef4 | ||
|
|
226b36903e | ||
|
|
de351d4438 | ||
|
|
5436d67e47 | ||
|
|
1c3d247071 | ||
|
|
d3e1e7c6fe | ||
|
|
a65c57ea0d | ||
|
|
8586c2550f | ||
|
|
fa6c4807f3 | ||
|
|
6e65f46e3f | ||
|
|
98e7b8e1a1 | ||
|
|
7dd42528db | ||
|
|
e580b76e9e | ||
|
|
c4d02c00ce |
No files matched your search
+37
-11
@@ -10,13 +10,16 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
with:
|
||||
submodules: 'recursive'
|
||||
fetch-depth: '0'
|
||||
@@ -40,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Restore cached libhevtun
|
||||
id: cache-libhevtun-restore
|
||||
uses: actions/cache/restore@v5
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/libs
|
||||
key: libhevtun-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('compile-hevtun.sh') }}
|
||||
@@ -52,7 +55,7 @@ jobs:
|
||||
|
||||
- name: Save libhevtun
|
||||
if: steps.cache-libhevtun-restore.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
uses: actions/cache/save@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/libs
|
||||
key: libhevtun-${{ runner.os }}-${{ env.NDK_HOME }}-${{ hashFiles('.git/modules/hev-socks5-tunnel/HEAD') }}-${{ hashFiles('compile-hevtun.sh') }}
|
||||
@@ -119,11 +122,34 @@ jobs:
|
||||
name: x86-apk
|
||||
path: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*x86*.apk
|
||||
|
||||
- name: Upload to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
if: github.event.inputs.release_tag != ''
|
||||
with:
|
||||
file: ${{ github.workspace }}/V2rayNG/app/build/outputs/apk/*/release/*.apk
|
||||
tag: ${{ github.event.inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
sign-and-release:
|
||||
needs: build
|
||||
runs-on: ubuntu-26.04
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.release_tag != ''
|
||||
|
||||
steps:
|
||||
- name: Download APK artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: release
|
||||
|
||||
- name: Sign APK files
|
||||
run: |
|
||||
printf '%s' "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --batch --import
|
||||
KEY=$(gpg --list-secret-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
|
||||
mkdir -p release-files
|
||||
|
||||
find release -type f -name '*.apk' | while read apk; do
|
||||
cp "$apk" release-files/
|
||||
gpg --batch --yes --local-user "$KEY" --detach-sign --output "release-files/$(basename "$apk").sig" "$apk"
|
||||
done
|
||||
|
||||
gpg --armor --export "$KEY" > release-files/v2rayN-public-key.asc
|
||||
|
||||
- name: Upload APKs, signatures and public key to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
file: release-files/*
|
||||
tag: ${{ inputs.release_tag }}
|
||||
file_glob: true
|
||||
prerelease: true
|
||||
@@ -11,6 +11,6 @@ jobs:
|
||||
go:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
- name: Validate Fastlane Supply Metadata
|
||||
uses: ashutoshgngwr/validate-fastlane-supply-metadata@v2.1.0
|
||||
+1
-1
Submodule AndroidLibXrayLite updated: 85f272f37c...3b5a9c858c.
@@ -9,24 +9,82 @@ A V2Ray client for Android, support [Xray core](https://github.com/XTLS/Xray-cor
|
||||
[](https://github.com/2dust/v2rayNG/releases)
|
||||
[](https://t.me/v2rayn)
|
||||
|
||||
### Telegram Channel
|
||||
[github_2dust](https://t.me/github_2dust)
|
||||
---
|
||||
|
||||
### Usage
|
||||
## Download / 下载
|
||||
|
||||
Download the latest release here:
|
||||
|
||||
在这里下载最新版本:
|
||||
|
||||
[https://github.com/2dust/v2rayNG/releases](https://github.com/2dust/v2rayNG/releases)
|
||||
|
||||
> [!TIP]
|
||||
> v2rayNG is the mobile version. For the desktop version, please visit the v2rayN \
|
||||
> v2rayNG 是手机版,电脑版请访问 v2rayN
|
||||
>
|
||||
> https://github.com/2dust/v2rayN
|
||||
|
||||
---
|
||||
|
||||
### Geoip and Geosite
|
||||
|
||||
#### Geoip and Geosite
|
||||
- geoip.dat and geosite.dat files are in `Android/data/com.v2ray.ang/files/assets` (path may differ on some Android device)
|
||||
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (Note it need a working proxy)
|
||||
- download feature will get enhanced version in this [repo](https://github.com/Loyalsoldier/v2ray-rules-dat) (note: it needs a working proxy)
|
||||
- latest official [domain list](https://github.com/Loyalsoldier/v2ray-rules-dat) and [ip list](https://github.com/Loyalsoldier/geoip) can be imported manually
|
||||
- possible to use third party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
||||
- possible to use a third-party dat file in the same folder, like [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
||||
|
||||
### More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
More in our [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
### Development guide
|
||||
### Geoip 与 Geosite
|
||||
|
||||
Android project under V2rayNG folder can be compiled directly in Android Studio, or using Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
||||
The aar can be compiled from the Golang project [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite).
|
||||
For a quick start, read guide for [Go Mobile](https://github.com/golang/go/wiki/Mobile) and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)
|
||||
- geoip.dat 和 geosite.dat 文件位于 `Android/data/com.v2ray.ang/files/assets`(部分设备路径可能不同)
|
||||
- 下载功能将获取该 [仓库](https://github.com/Loyalsoldier/v2ray-rules-dat) 中的增强版本(注意:此功能需要一个可用的代理)
|
||||
- 最新官方 [域名列表](https://github.com/Loyalsoldier/v2ray-rules-dat) 和 [IP 列表](https://github.com/Loyalsoldier/geoip) 可手动导入
|
||||
- 也可在同一文件夹中使用第三方 dat 文件,例如 [h2y](https://guide.v2fly.org/routing/sitedata.html#%E5%A4%96%E7%BD%AE%E7%9A%84%E5%9F%9F%E5%90%8D%E6%96%87%E4%BB%B6)
|
||||
|
||||
v2rayNG can run on Android Emulators. For WSA, VPN permission need to be granted via
|
||||
`appops set [package name] ACTIVATE_VPN allow`
|
||||
更多内容请见我们的 [wiki](https://github.com/2dust/v2rayNG/wiki)
|
||||
|
||||
---
|
||||
|
||||
## Development guide / 开发指南
|
||||
|
||||
### Note
|
||||
|
||||
- Android project under the V2rayNG folder can be compiled directly in Android Studio, or using the Gradle wrapper. But the v2ray core inside the aar is (probably) outdated.
|
||||
- The aar can be compiled from the Golang project [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite). For a quick start, read the guides for [Go Mobile](https://github.com/golang/go/wiki/Mobile) and [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/).
|
||||
- v2rayNG can run on Android Emulators. For WSA, VPN permission needs to be granted via `appops set [package name] ACTIVATE_VPN allow`.
|
||||
|
||||
### 提示
|
||||
|
||||
- V2rayNG 文件夹下的 Android 项目可直接在 Android Studio 中编译,或使用 Gradle wrapper 编译。但 aar 内置的 v2ray core(可能)已过时。
|
||||
- aar 可由 Golang 项目 [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) 或 [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite) 编译而成。快速入门可参考 [Go Mobile](https://github.com/golang/go/wiki/Mobile) 指南和 [Makefiles for Go Developers](https://tutorialedge.net/golang/makefiles-for-go-developers/)。
|
||||
- v2rayNG 可在 Android 模拟器上运行。对于 WSA,需要通过 `appops set [package name] ACTIVATE_VPN allow` 授予 VPN 权限。
|
||||
|
||||
---
|
||||
|
||||
|
||||
## GPG Verification / GPG 签名校验
|
||||
|
||||
Release files are signed with GPG to verify authenticity and integrity, helping prevent mirror, ISP, or CDN hijacking.
|
||||
|
||||
发布文件已使用 GPG 签名,可用于校验文件真实性与完整性,预防镜像站、运营商或 CDN 劫持。
|
||||
|
||||
### Fingerprint / 公钥指纹
|
||||
|
||||
```text
|
||||
7694 5E9F 3E9A 168F 8070 F195 805D 661C
|
||||
134D FAF6 8903 C199 463C 31E5 AE90 3AE0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Community / 社区
|
||||
|
||||
Telegram Group / Telegram 群组:
|
||||
|
||||
[https://t.me/v2rayN](https://t.me/v2rayN)
|
||||
|
||||
Telegram Channel / Telegram 频道:
|
||||
|
||||
[https://t.me/github_2dust](https://t.me/github_2dust)
|
||||
@@ -12,8 +12,8 @@ android {
|
||||
applicationId = "com.v2ray.ang"
|
||||
minSdk = 24
|
||||
targetSdk = 37
|
||||
versionCode = 734
|
||||
versionName = "2.2.4"
|
||||
versionCode = 736
|
||||
versionName = "2.2.6"
|
||||
multiDexEnabled = true
|
||||
|
||||
val abiFilterList = (properties["ABI_FILTERS"] as? String)?.split(';')
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"
|
||||
@@ -201,6 +202,17 @@
|
||||
android:value="proxy" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.CoreRootService"
|
||||
android:exported="false"
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="@string/app_name"
|
||||
android:process=":RunSoLibV2RayDaemon">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="proxy" />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.CoreTestService"
|
||||
android:exported="false"
|
||||
|
||||
@@ -43,6 +43,7 @@ object AppConfig {
|
||||
const val PREF_FRAGMENT_PACKETS = "pref_fragment_packets"
|
||||
const val PREF_FRAGMENT_LENGTH = "pref_fragment_length"
|
||||
const val PREF_FRAGMENT_INTERVAL = "pref_fragment_interval"
|
||||
const val PREF_FRAGMENT_MAXSPLIT = "pref_fragment_maxsplit"
|
||||
const val SUBSCRIPTION_UPDATE_TASK_NAME = "subscription_updater"
|
||||
const val SUBSCRIPTION_MIN_INTERVAL_MINUTES = 15L
|
||||
const val PREF_SPEED_ENABLED = "pref_speed_enabled"
|
||||
@@ -69,6 +70,8 @@ object AppConfig {
|
||||
const val PREF_LOGLEVEL = "pref_core_loglevel"
|
||||
const val PREF_OUTBOUND_DOMAIN_RESOLVE_METHOD = "pref_outbound_domain_resolve_method"
|
||||
const val PREF_MODE = "pref_mode"
|
||||
const val PREF_ROOT_MODE_ENABLE = "pref_root_mode_enabled"
|
||||
const val PREF_ROOT_LAN_SHARING = "pref_root_lan_sharing"
|
||||
const val PREF_IS_BOOTED = "pref_is_booted"
|
||||
const val PREF_CHECK_UPDATE_PRE_RELEASE = "pref_check_update_pre_release"
|
||||
const val PREF_GEO_FILES_SOURCES = "pref_geo_files_sources"
|
||||
@@ -195,6 +198,28 @@ object AppConfig {
|
||||
const val VPN = "VPN"
|
||||
const val VPN_MTU = 1500
|
||||
|
||||
/** Root (system-wide) mode runtime constants. */
|
||||
const val ROOT_RUNTIME_DIR = "root"
|
||||
const val ROOT_IPTABLES_CHAIN = "V2RAY_NG"
|
||||
const val ROOT_FWMARK = 255 // defensive RETURN tag; hev's only upstream socket is loopback (already bypassed)
|
||||
const val ROOT_MARK_ROUTE = 1 // packets we want pushed into the tun device
|
||||
const val ROOT_ROUTE_TABLE = 2024
|
||||
const val ROOT_RULE_PRIORITY = 1000
|
||||
const val ROOT_TUN_NAME = "v2raytun0"
|
||||
const val ROOT_TUN_ADDR_V4 = "198.18.0.1/15"
|
||||
const val ROOT_TUN_ADDR_V6 = "fdfe:dcba:9876::1/64"
|
||||
|
||||
// hev-socks5-tunnel run as a standalone root binary (reuses the same project already
|
||||
// bundled for the VPN hev path; distinct filename from the JNI lib to avoid collision).
|
||||
const val ROOT_TUN2SOCKS_BIN = "libhevsockstun.so"
|
||||
const val ROOT_FWD_CHAIN = "V2RAY_NG_FWD" // FORWARD chain for LAN/tethering sharing
|
||||
const val ROOT_DNS_CHAIN = "V2RAY_NG_DNS" // nat chain for tethered-client DNS DNAT
|
||||
const val ROOT_V6_CHAIN = "V2RAY_NG6" // ip6tables filter/OUTPUT chain: blackhole native IPv6 when it isn't tunneled
|
||||
const val ROOT_V6_FWD_CHAIN = "V2RAY_NG6_FWD" // ip6tables FORWARD chain: route or reject tethered clients' native IPv6
|
||||
const val ROOT_V6_PRE_CHAIN = "V2RAY_NG6_PRE" // ip6tables mangle/PREROUTING chain: mark forwarded clients' IPv6 into the tun
|
||||
const val ROOT_LAN_DNS = "1.1.1.1" // fallback resolver for tethered clients when no plain-IPv4 DNS is configured
|
||||
const val ROOT_OOM_SCORE = "-1000" // oom_score_adj that makes the LMK never kill us
|
||||
|
||||
/** hev-sock5-tunnel read-write-timeout value */
|
||||
const val HEVTUN_RW_TIMEOUT = "300,60"
|
||||
|
||||
|
||||
@@ -43,11 +43,13 @@ object CoreConfigContextBuilder {
|
||||
|
||||
// Step 2: Resolve all non-builtin routing outbound tags.
|
||||
val routingResolvedOutbounds = resolveRoutingOutbounds()
|
||||
val routingDomainRules = collectRoutingDomainRulesForDns()
|
||||
|
||||
return CoreConfigContext(
|
||||
context = context,
|
||||
guid = guid,
|
||||
resolvedOutbounds = listOf(primaryResolvedOutbound) + routingResolvedOutbounds,
|
||||
routingDomainRules = routingDomainRules,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -214,4 +216,34 @@ object CoreConfigContextBuilder {
|
||||
return listOf(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect enabled routing domain rules in original order for DNS segmentation.
|
||||
*
|
||||
* outbounds are normalized into three tags only: proxy / direct / block.
|
||||
*/
|
||||
private fun collectRoutingDomainRulesForDns(): List<CoreConfigContext.RoutingDomainRule> {
|
||||
val rulesetItems = MmkvManager.decodeRoutingRulesets() ?: return emptyList()
|
||||
val result = mutableListOf<CoreConfigContext.RoutingDomainRule>()
|
||||
|
||||
rulesetItems
|
||||
.asSequence()
|
||||
.filter { it.enabled }
|
||||
.filter { !it.domain.isNullOrEmpty() }
|
||||
.forEach { rule ->
|
||||
val normalizedOutboundTag = when (rule.outboundTag) {
|
||||
AppConfig.TAG_DIRECT -> AppConfig.TAG_DIRECT
|
||||
AppConfig.TAG_BLOCKED -> AppConfig.TAG_BLOCKED
|
||||
else -> AppConfig.TAG_PROXY
|
||||
}
|
||||
result.add(
|
||||
CoreConfigContext.RoutingDomainRule(
|
||||
domain = rule.domain.orEmpty(),
|
||||
outboundTag = normalizedOutboundTag
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -166,8 +166,9 @@ object CoreConfigManager {
|
||||
// User routing rules (policyGroupBalancerTags rewrites TAG_PROXY→balancer when main is POLICYGROUP).
|
||||
configureRouting(configContext, v2rayConfig, policyGroupBalancerTags)
|
||||
configureFakeDns(v2rayConfig)
|
||||
configureDns(v2rayConfig, policyGroupBalancerTags)
|
||||
configureLocalDns(v2rayConfig)
|
||||
configureDns(configContext, v2rayConfig, policyGroupBalancerTags)
|
||||
configureLocalDns(configContext, v2rayConfig)
|
||||
configureRootModeDns(v2rayConfig)
|
||||
|
||||
// (added by getDns / getCustomLocalDns) to use the balancer, then add
|
||||
// the catch-all balancer rule.
|
||||
@@ -452,8 +453,10 @@ object CoreConfigManager {
|
||||
val vpn = SettingsManager.isVpnMode()
|
||||
val useHev = SettingsManager.isUsingHevTun()
|
||||
val forcedByHev = vpn && useHev
|
||||
val forcedBySocksRoot = SettingsManager.isRootMode()
|
||||
|| MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING)
|
||||
|
||||
val enableLocalProxy = forcedByHev || MmkvManager.decodeSettingsBool(AppConfig.PREF_ENABLE_LOCAL_PROXY, true)
|
||||
val enableLocalProxy = forcedByHev || forcedBySocksRoot || MmkvManager.decodeSettingsBool(AppConfig.PREF_ENABLE_LOCAL_PROXY, true)
|
||||
|
||||
val socksPort = SettingsManager.getSocksPort()
|
||||
val socksUsername = SettingsManager.getSocksUsername()
|
||||
@@ -567,16 +570,20 @@ object CoreConfigManager {
|
||||
/**
|
||||
* Configure local DNS inbounds, outbounds, and routing rules.
|
||||
*/
|
||||
private fun configureLocalDns(v2rayConfig: V2rayConfig) {
|
||||
private fun configureLocalDns(configContext: CoreConfigContext, v2rayConfig: V2rayConfig) {
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_LOCAL_DNS_ENABLED) != true) {
|
||||
return
|
||||
}
|
||||
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_FAKE_DNS_ENABLED) == true) {
|
||||
val geositeCn = arrayListOf(AppConfig.GEOSITE_CN)
|
||||
val proxyDomain = collectUserRuleDomainsByTag(AppConfig.TAG_PROXY)
|
||||
val directDomain = collectUserRuleDomainsByTag(AppConfig.TAG_DIRECT)
|
||||
val finalDomain = geositeCn.plus(proxyDomain).plus(directDomain).distinct()
|
||||
val routingDomains = configContext.routingDomainRules
|
||||
.asSequence()
|
||||
.filter { it.outboundTag != AppConfig.TAG_BLOCKED }
|
||||
.flatMap { it.domain.asSequence() }
|
||||
.toList()
|
||||
.distinct()
|
||||
val finalDomain = geositeCn + routingDomains
|
||||
// fakedns with all domains to make it always top priority
|
||||
v2rayConfig.dns?.servers?.add(
|
||||
0,
|
||||
@@ -622,6 +629,39 @@ object CoreConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* In the root mode the whole device's traffic (incl. raw DNS) is funneled
|
||||
* into the core's SOCKS inbound, exactly like the VPN+hev path. Hijack port-53 to the
|
||||
* core's DNS module so queries are resolved via the configured resolver through the
|
||||
* proxy instead of leaking to (or being mis-resolved by) the local network resolver.
|
||||
* Independent of the local-DNS toggle, which is not exposed for root mode.
|
||||
*/
|
||||
private fun configureRootModeDns(v2rayConfig: V2rayConfig) {
|
||||
if (!SettingsManager.isRootMode()) return
|
||||
|
||||
if (v2rayConfig.routing.rules.none { it.outboundTag == "dns-out" && it.port == "53" }) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
0,
|
||||
V2rayConfig.RoutingBean.RulesBean(
|
||||
inboundTag = arrayListOf("socks"),
|
||||
outboundTag = "dns-out",
|
||||
port = "53",
|
||||
)
|
||||
)
|
||||
}
|
||||
if (v2rayConfig.outbounds.none { it.protocol == "dns" && it.tag == "dns-out" }) {
|
||||
v2rayConfig.outbounds.add(
|
||||
V2rayConfig.OutboundBean(
|
||||
protocol = "dns",
|
||||
tag = "dns-out",
|
||||
settings = null,
|
||||
streamSettings = null,
|
||||
mux = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove speed-test runtime sections when the feature is disabled.
|
||||
*/
|
||||
@@ -632,6 +672,7 @@ object CoreConfigManager {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Configure DNS servers, hosts, and DNS routing rules.
|
||||
*/
|
||||
@@ -771,7 +812,188 @@ object CoreConfigManager {
|
||||
)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Configure DNS servers, hosts, and DNS routing rules.
|
||||
*/
|
||||
private fun configureDns(
|
||||
configContext: CoreConfigContext,
|
||||
v2rayConfig: V2rayConfig,
|
||||
policyGroupBalancerTags: Map<String, String>,
|
||||
) {
|
||||
val servers = ArrayList<Any>()
|
||||
val remoteDns = SettingsManager.getRemoteDnsServers()
|
||||
val domesticDns = SettingsManager.getDomesticDnsServers()
|
||||
|
||||
remoteDns.forEach { servers.add(it) }
|
||||
|
||||
val hosts = buildDnsHostsFromRoutingRules(configContext)
|
||||
val cnDomesticDnsTags = buildDnsCnModeFromRoutingRules(configContext, servers, domesticDns)
|
||||
val domesticDnsTags = buildDnsFromRoutingRules(
|
||||
configContext = configContext,
|
||||
servers = servers,
|
||||
remoteDns = remoteDns,
|
||||
domesticDns = domesticDns
|
||||
)
|
||||
domesticDnsTags.addAll(cnDomesticDnsTags)
|
||||
|
||||
v2rayConfig.dns = V2rayConfig.DnsBean(
|
||||
servers = servers,
|
||||
hosts = hosts,
|
||||
tag = AppConfig.TAG_DNS,
|
||||
enableParallelQuery = if ((domesticDns.size + remoteDns.size) > 2) true else null
|
||||
)
|
||||
|
||||
if (domesticDnsTags.isNotEmpty()) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_DIRECT,
|
||||
inboundTag = ArrayList(domesticDnsTags),
|
||||
domain = null
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val dnsProxyBalancerTag = policyGroupBalancerTags[AppConfig.TAG_PROXY]
|
||||
if (dnsProxyBalancerTag != null) {
|
||||
v2rayConfig.routing.rules.add(
|
||||
V2rayConfig.RoutingBean.RulesBean(
|
||||
balancerTag = dnsProxyBalancerTag,
|
||||
inboundTag = arrayListOf(AppConfig.TAG_DNS),
|
||||
domain = null
|
||||
)
|
||||
)
|
||||
} else {
|
||||
v2rayConfig.routing.rules.add(
|
||||
V2rayConfig.RoutingBean.RulesBean(
|
||||
outboundTag = AppConfig.TAG_PROXY,
|
||||
inboundTag = arrayListOf(AppConfig.TAG_DNS),
|
||||
domain = null
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDnsHostsFromRoutingRules(configContext: CoreConfigContext): MutableMap<String, Any> {
|
||||
val hosts = mutableMapOf<String, Any>()
|
||||
|
||||
val blockDomains = configContext.routingDomainRules
|
||||
.asSequence()
|
||||
.filter { it.outboundTag == AppConfig.TAG_BLOCKED }
|
||||
.flatMap { it.domain.asSequence() }
|
||||
.toList()
|
||||
if (blockDomains.isNotEmpty()) {
|
||||
hosts.putAll(blockDomains.map { it to AppConfig.LOOPBACK })
|
||||
}
|
||||
|
||||
hosts[AppConfig.GOOGLEAPIS_CN_DOMAIN] = AppConfig.GOOGLEAPIS_COM_DOMAIN
|
||||
hosts[AppConfig.DNS_ALIDNS_DOMAIN] = AppConfig.DNS_ALIDNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CISCO_SSE_DOMAIN] = AppConfig.DNS_CISCO_SSE_ADDRESSES
|
||||
hosts[AppConfig.DNS_CISCO_UMBRELLA_DOMAIN] = AppConfig.DNS_CISCO_UMBRELLA_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_ONE_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONE_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_ONEDOT_DNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_COM_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_COM_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_DNS_DOMAIN] = AppConfig.DNS_CLOUDFLARE_DNS_ADDRESSES
|
||||
hosts[AppConfig.DNS_CLOUDFLARE_WARP_DOMAIN] = AppConfig.DNS_CLOUDFLARE_WARP_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOH_DOMAIN] = AppConfig.DNS_DNSPOD_DOH_ADDRESSES
|
||||
hosts[AppConfig.DNS_DNSPOD_DOT_DOMAIN] = AppConfig.DNS_DNSPOD_DOT_ADDRESSES
|
||||
hosts[AppConfig.DNS_GOOGLE_DOMAIN] = AppConfig.DNS_GOOGLE_ADDRESSES
|
||||
hosts[AppConfig.DNS_QUAD9_DOMAIN] = AppConfig.DNS_QUAD9_ADDRESSES
|
||||
hosts[AppConfig.DNS_SB_DOMAIN] = AppConfig.DNS_SB_ADDRESSES
|
||||
hosts[AppConfig.DNS_YANDEX_DOMAIN] = AppConfig.DNS_YANDEX_ADDRESSES
|
||||
|
||||
val userHosts = MmkvManager.decodeSettingsString(AppConfig.PREF_DNS_HOSTS)
|
||||
if (userHosts.isNotNullEmpty()) {
|
||||
val userHostsMap = userHosts?.split(",").orEmpty()
|
||||
.filter { it.isNotEmpty() }
|
||||
.filter { it.contains(":") }
|
||||
.associate { it.split(":").let { (k, v) -> k to v } }
|
||||
hosts.putAll(userHostsMap)
|
||||
}
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
private fun buildDnsCnModeFromRoutingRules(configContext: CoreConfigContext, servers: ArrayList<Any>, domesticDns: List<String>, ): List<String> {
|
||||
val cnRegionFilter = { domain: String ->
|
||||
domain.startsWith("geosite:") && (domain.endsWith("-cn") || domain.endsWith("@cn"))
|
||||
|| domain == AppConfig.GEOSITE_CN
|
||||
}
|
||||
val isCnRoutingMode = configContext.routingDomainRules
|
||||
.asSequence()
|
||||
.filter { it.outboundTag == AppConfig.TAG_DIRECT }
|
||||
.flatMap { it.domain.asSequence() }
|
||||
.any { it == AppConfig.GEOSITE_CN }
|
||||
|
||||
if (!isCnRoutingMode) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val geoipCn = arrayListOf(AppConfig.GEOIP_CN)
|
||||
val cnDomains = configContext.routingDomainRules
|
||||
.asSequence()
|
||||
.filter { it.outboundTag == AppConfig.TAG_DIRECT }
|
||||
.flatMap { it.domain.asSequence() }
|
||||
.filter { cnRegionFilter(it) }
|
||||
.toList()
|
||||
if (cnDomains.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val cnDomesticDnsTags = mutableListOf<String>()
|
||||
domesticDns.forEachIndexed { index, address ->
|
||||
val cnDomesticDnsTag = "${AppConfig.TAG_DOMESTIC_DNS}_cn_expect_${index}"
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
address = address,
|
||||
domains = cnDomains,
|
||||
expectIPs = geoipCn,
|
||||
skipFallback = true,
|
||||
tag = cnDomesticDnsTag
|
||||
)
|
||||
)
|
||||
cnDomesticDnsTags.add(cnDomesticDnsTag)
|
||||
}
|
||||
return cnDomesticDnsTags
|
||||
}
|
||||
|
||||
private fun buildDnsFromRoutingRules(
|
||||
configContext: CoreConfigContext,
|
||||
servers: ArrayList<Any>,
|
||||
remoteDns: List<String>,
|
||||
domesticDns: List<String>,
|
||||
): MutableList<String> {
|
||||
val domesticDnsTags = mutableListOf<String>()
|
||||
configContext.routingDomainRules.forEachIndexed { ruleIndex, rule ->
|
||||
when (rule.outboundTag) {
|
||||
AppConfig.TAG_DIRECT -> {
|
||||
domesticDns.forEachIndexed { dnsIndex, address ->
|
||||
val tag = "${AppConfig.TAG_DOMESTIC_DNS}_${ruleIndex}_$dnsIndex"
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
address = address,
|
||||
domains = rule.domain,
|
||||
skipFallback = true,
|
||||
tag = tag
|
||||
)
|
||||
)
|
||||
domesticDnsTags.add(tag)
|
||||
}
|
||||
}
|
||||
AppConfig.TAG_BLOCKED -> Unit
|
||||
else -> {
|
||||
servers.add(
|
||||
V2rayConfig.DnsBean.ServersBean(
|
||||
address = remoteDns.first(),
|
||||
domains = rule.domain,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return domesticDnsTags
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
|
||||
@@ -368,37 +368,40 @@ object CoreOutboundBuilder {
|
||||
profileItem.kcpMtu?.let { kcpSetting.mtu = it }
|
||||
profileItem.kcpTti?.let { kcpSetting.tti = it }
|
||||
streamSettings.kcpSettings = kcpSetting
|
||||
val udpMaskList = mutableListOf<OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean>()
|
||||
val udpMaskList =
|
||||
mutableListOf<OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean>()
|
||||
if (!headerType.isNullOrEmpty() && headerType != "none") {
|
||||
val kcpHeaderType = when {
|
||||
headerType == "wechat-video" -> "header-wechat"
|
||||
else -> "header-$headerType"
|
||||
headerType == "wechat-video" -> "wechat"
|
||||
else -> headerType
|
||||
}
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = kcpHeaderType,
|
||||
settings = if (headerType == "dns" && !host.isNullOrEmpty()) {
|
||||
type = "mkcp-legacy",
|
||||
settings =
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||
domain = host
|
||||
header = kcpHeaderType,
|
||||
value = if (headerType == "dns" && !host.isNullOrEmpty()) {
|
||||
host
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
if (seed.isNullOrEmpty()) {
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = "mkcp-original"
|
||||
type = "mkcp-legacy"
|
||||
)
|
||||
)
|
||||
} else {
|
||||
udpMaskList.add(
|
||||
OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
type = "mkcp-aes128gcm",
|
||||
type = "mkcp-legacy",
|
||||
settings = OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean.MaskSettingsBean(
|
||||
password = seed
|
||||
value = seed
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -612,10 +615,6 @@ object CoreOutboundBuilder {
|
||||
&& packets == "tlshello"
|
||||
) {
|
||||
packets = "1-3"
|
||||
} else if (streamSettings.security == AppConfig.TLS
|
||||
&& packets != "tlshello"
|
||||
) {
|
||||
packets = "tlshello"
|
||||
}
|
||||
|
||||
val fragmentMask = OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
@@ -625,7 +624,9 @@ object CoreOutboundBuilder {
|
||||
length = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_LENGTH)
|
||||
?: "50-100",
|
||||
delay = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_INTERVAL)
|
||||
?: "10-20"
|
||||
?: "10-20",
|
||||
maxSplit = MmkvManager.decodeSettingsString(AppConfig.PREF_FRAGMENT_MAXSPLIT)
|
||||
?: "10"
|
||||
)
|
||||
)
|
||||
val noiseMask = OutboundBean.StreamSettingsBean.FinalMaskBean.MaskBean(
|
||||
|
||||
@@ -15,7 +15,6 @@ import com.v2ray.ang.R
|
||||
import com.v2ray.ang.contracts.ServiceControl
|
||||
import com.v2ray.ang.dto.OutboundTrafficStat
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.toast
|
||||
import com.v2ray.ang.extension.toastError
|
||||
@@ -23,7 +22,9 @@ import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.NotificationManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
import com.v2ray.ang.root.RootManager
|
||||
import com.v2ray.ang.service.CoreProxyOnlyService
|
||||
import com.v2ray.ang.service.CoreRootService
|
||||
import com.v2ray.ang.service.CoreVpnService
|
||||
import com.v2ray.ang.service.DialerNativeService
|
||||
import com.v2ray.ang.service.DialerWebviewService
|
||||
@@ -172,8 +173,16 @@ object CoreServiceManager {
|
||||
context.toast(R.string.toast_services_start)
|
||||
}
|
||||
|
||||
val isVpnMode = SettingsManager.isVpnMode()
|
||||
val intent = if (isVpnMode) {
|
||||
val isRootMode = SettingsManager.isRootMode()
|
||||
if (isRootMode && !RootManager.isRootAvailable()) {
|
||||
LogUtil.e(AppConfig.TAG, "StartCore-Manager: root mode requires root but none available")
|
||||
error(context.getString(R.string.toast_root_required))
|
||||
}
|
||||
|
||||
val intent = if (isRootMode) {
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Starting Root service")
|
||||
Intent(context.applicationContext, CoreRootService::class.java)
|
||||
} else if (SettingsManager.isVpnMode()) {
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Manager: Starting VPN service")
|
||||
Intent(context.applicationContext, CoreVpnService::class.java)
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ data class CoreConfigContext(
|
||||
val guid: String,
|
||||
val isCustom: Boolean = false,
|
||||
val resolvedOutbounds: List<ResolvedOutbound> = emptyList(),
|
||||
val routingDomainRules: List<RoutingDomainRule> = emptyList(),
|
||||
) {
|
||||
data class ResolvedOutbound(
|
||||
val tag: String,
|
||||
@@ -16,4 +17,9 @@ data class CoreConfigContext(
|
||||
val resolvedProfiles: List<ProfileItem>,
|
||||
val resolvedType: CoreResolvedType,
|
||||
)
|
||||
}
|
||||
|
||||
data class RoutingDomainRule(
|
||||
val domain: List<String>,
|
||||
val outboundTag: String,
|
||||
)
|
||||
}
|
||||
@@ -256,7 +256,6 @@ data class V2rayConfig(
|
||||
var verifyPeerCertByName: String? = null,
|
||||
var pinnedPeerCertSha256: String? = null,
|
||||
// REALITY settings
|
||||
val show: Boolean = false,
|
||||
var publicKey: String? = null,
|
||||
var shortId: String? = null,
|
||||
var spiderX: String? = null,
|
||||
@@ -296,12 +295,13 @@ data class V2rayConfig(
|
||||
) {
|
||||
data class MaskSettingsBean(
|
||||
val password: String? = null,
|
||||
val domain: String? = null,
|
||||
val header: String? = null,
|
||||
val value: String? = null,
|
||||
// fragment
|
||||
val packets: String? = null,
|
||||
val length: String? = null,
|
||||
val delay: String? = null,
|
||||
// val maxSplit: String? = null,
|
||||
val maxSplit: String? = null,
|
||||
// noise
|
||||
val reset: Int? = null,
|
||||
val noise: List<NoiseMaskBean>? = null
|
||||
|
||||
@@ -17,6 +17,12 @@ enum class PermissionType {
|
||||
POST_NOTIFICATIONS {
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
override fun getPermission(): String = Manifest.permission.POST_NOTIFICATIONS
|
||||
},
|
||||
|
||||
/** Local network access permission (Android 17+) */
|
||||
ACCESS_LOCAL_NETWORK {
|
||||
@RequiresApi(Build.VERSION_CODES.CINNAMON_BUN)
|
||||
override fun getPermission(): String = Manifest.permission.ACCESS_LOCAL_NETWORK
|
||||
};
|
||||
|
||||
/** Return the actual Android permission string */
|
||||
@@ -27,6 +33,7 @@ enum class PermissionType {
|
||||
return when (this) {
|
||||
CAMERA -> "Camera"
|
||||
POST_NOTIFICATIONS -> "Notification"
|
||||
ACCESS_LOCAL_NETWORK -> "Local Network"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.net.URI
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.enums.NetworkType
|
||||
import com.v2ray.ang.extension.idnHost
|
||||
import com.v2ray.ang.extension.nullIfBlank
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
|
||||
@@ -286,9 +286,13 @@ object MmkvManager {
|
||||
*/
|
||||
fun removeAllServer(): Int {
|
||||
val count = profileFullStorage.allKeys()?.count() ?: 0
|
||||
mainStorage.clearAll()
|
||||
profileFullStorage.clearAll()
|
||||
serverAffStorage.clearAll()
|
||||
serverRawStorage.clearAll()
|
||||
|
||||
decodeSubscriptions().forEach { sub ->
|
||||
encodeServerList(mutableListOf(), sub.guid)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ object NotificationManager {
|
||||
stopV2RayPendingIntent
|
||||
)
|
||||
.addAction(
|
||||
R.drawable.ic_delete_24dp,
|
||||
R.drawable.ic_restore_24dp,
|
||||
service.getString(R.string.title_service_restart),
|
||||
restartV2RayPendingIntent
|
||||
)
|
||||
|
||||
@@ -487,6 +487,13 @@ object SettingsManager {
|
||||
return mode == null || mode == VPN
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a root (system-wide) run mode is selected.
|
||||
*/
|
||||
fun isRootMode(): Boolean {
|
||||
return MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_MODE_ENABLE, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if process routing can be used.
|
||||
*/
|
||||
@@ -527,6 +534,7 @@ object SettingsManager {
|
||||
ensureDefaultValue(AppConfig.PREF_MUX_XUDP_CONCURRENCY, "8")
|
||||
ensureDefaultValue(AppConfig.PREF_FRAGMENT_LENGTH, "50-100")
|
||||
ensureDefaultValue(AppConfig.PREF_FRAGMENT_INTERVAL, "10-20")
|
||||
ensureDefaultValue(AppConfig.PREF_FRAGMENT_MAXSPLIT, "10")
|
||||
}
|
||||
|
||||
private fun ensureDefaultValue(key: String, default: String) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import com.v2ray.ang.dto.UrlContentRequest
|
||||
import com.v2ray.ang.util.HttpUtil
|
||||
import com.v2ray.ang.util.JsonUtil
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import kotlinx.coroutines.currentCoroutineContext
|
||||
import kotlinx.coroutines.isActive
|
||||
import java.io.IOException
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.Socket
|
||||
@@ -15,29 +13,6 @@ import java.net.UnknownHostException
|
||||
|
||||
object SpeedtestManager {
|
||||
|
||||
private val tcpTestingSockets = ArrayList<Socket?>()
|
||||
|
||||
/**
|
||||
* Measures the TCP connection time to a given URL and port.
|
||||
*
|
||||
* @param url The URL to connect to.
|
||||
* @param port The port to connect to.
|
||||
* @return The connection time in milliseconds, or -1 if the connection failed.
|
||||
*/
|
||||
suspend fun tcping(url: String, port: Int): Long {
|
||||
var time = -1L
|
||||
for (k in 0 until 2) {
|
||||
val one = socketConnectTime(url, port)
|
||||
if (!currentCoroutineContext().isActive) {
|
||||
break
|
||||
}
|
||||
if (one != -1L && (time == -1L || one < time)) {
|
||||
time = one
|
||||
}
|
||||
}
|
||||
return time
|
||||
}
|
||||
|
||||
/**
|
||||
* Measures the time taken to establish a TCP connection to a given URL and port.
|
||||
*
|
||||
@@ -45,42 +20,34 @@ object SpeedtestManager {
|
||||
* @param port The port to connect to.
|
||||
* @return The connection time in milliseconds, or -1 if the connection failed.
|
||||
*/
|
||||
fun socketConnectTime(url: String, port: Int): Long {
|
||||
fun socketConnectTime(url: String, port: Int, timeoutMs: Int = 1500): Long {
|
||||
var socket: Socket? = null
|
||||
val start = System.currentTimeMillis()
|
||||
|
||||
try {
|
||||
val socket = Socket()
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.add(socket)
|
||||
}
|
||||
val start = System.currentTimeMillis()
|
||||
socket.connect(InetSocketAddress(url, port), 3000)
|
||||
val time = System.currentTimeMillis() - start
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.remove(socket)
|
||||
}
|
||||
socket.close()
|
||||
return time
|
||||
socket = Socket()
|
||||
socket.connect(InetSocketAddress(url, port), timeoutMs)
|
||||
|
||||
return System.currentTimeMillis() - start
|
||||
} catch (e: UnknownHostException) {
|
||||
LogUtil.e(AppConfig.TAG, "Unknown host: $url", e)
|
||||
} catch (e: IOException) {
|
||||
LogUtil.e(AppConfig.TAG, "socketConnectTime IOException: $e")
|
||||
LogUtil.e(AppConfig.TAG, "socketConnectTime IOException: ${e.message}")
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "Failed to establish socket connection to $url:$port", e)
|
||||
} finally {
|
||||
socket?.let { s ->
|
||||
try {
|
||||
if (!s.isClosed) {
|
||||
s.close()
|
||||
}
|
||||
} catch (closeEx: IOException) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all TCP sockets that are currently being tested.
|
||||
*/
|
||||
fun closeAllTcpSockets() {
|
||||
synchronized(this) {
|
||||
tcpTestingSockets.forEach {
|
||||
it?.close()
|
||||
}
|
||||
tcpTestingSockets.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun getRemoteIPInfo(): String? {
|
||||
val url = MmkvManager.decodeSettingsString(AppConfig.PREF_IP_API_URL)
|
||||
.takeIf { !it.isNullOrBlank() } ?: AppConfig.IP_API_URL
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.v2ray.ang.root
|
||||
|
||||
import android.content.Context
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
|
||||
object RootLanSharing {
|
||||
|
||||
private var lanSharingStarted = false
|
||||
private var lanShareJob: Job? = null
|
||||
|
||||
/**
|
||||
* Optional root feature: share the proxy with tethered LAN/USB clients while the
|
||||
* device itself stays on the VpnService. Runs a dedicated client hev-socks5-tunnel
|
||||
* off the main thread so the su calls don't block service startup.
|
||||
* The cheap, usually-false preference is checked first so the common path
|
||||
* short-circuits before touching root state.
|
||||
*/
|
||||
fun startClientSharing(context: Context): Boolean {
|
||||
if (MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING) && RootManager.cachedRoot()) {
|
||||
if (lanShareJob != null) return false
|
||||
|
||||
lanSharingStarted = true
|
||||
lanShareJob = CoroutineScope(Dispatchers.IO).launch { RootProxyManager.startClientSharing(context) }
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove LAN/tethering sharing rules + helper before stopping the core. Wait for the
|
||||
* async setup to finish first, otherwise a stop during setup tears down before the
|
||||
* rules are installed and they leak (orphan FORWARD/policy-routing rules + client tun).
|
||||
*/
|
||||
fun stopClientSharing(context: Context) {
|
||||
if (!lanSharingStarted) return
|
||||
|
||||
lanSharingStarted = false
|
||||
runBlocking { lanShareJob?.cancelAndJoin() }
|
||||
lanShareJob = null
|
||||
RootProxyManager.stop(context)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.v2ray.ang.root
|
||||
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.root.RootManager.isRootAvailable
|
||||
import com.v2ray.ang.root.RootManager.refresh
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Detects whether the device grants root (`su`) access.
|
||||
*
|
||||
* The result is cached after the first successful probe. Probing spawns `su` and
|
||||
* blocks, so [refresh] (a suspending call on [Dispatchers.IO]) should be used from UI
|
||||
* code; [isRootAvailable] may block and must not be called on the main thread the first time.
|
||||
*/
|
||||
object RootManager {
|
||||
|
||||
@Volatile
|
||||
private var cached: Boolean? = null
|
||||
|
||||
/** Last known result without probing. Defaults to false when never probed. */
|
||||
fun cachedRoot(): Boolean = cached ?: false
|
||||
|
||||
/**
|
||||
* Returns whether root is available, probing once if unknown.
|
||||
* May block while `su` is spawned; avoid calling on the main thread before a probe.
|
||||
*/
|
||||
fun isRootAvailable(forceRefresh: Boolean = false): Boolean {
|
||||
if (!forceRefresh) cached?.let { return it }
|
||||
val result = probe()
|
||||
cached = result
|
||||
return result
|
||||
}
|
||||
|
||||
/** Probes root off the main thread, updates the cache, and returns the result. */
|
||||
suspend fun refresh(): Boolean = withContext(Dispatchers.IO) {
|
||||
val result = probe()
|
||||
cached = result
|
||||
result
|
||||
}
|
||||
|
||||
private fun probe(): Boolean {
|
||||
return try {
|
||||
val process = ProcessBuilder("su", "-c", "id -u")
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
val output = process.inputStream.bufferedReader().use { it.readText() }.trim()
|
||||
val finished = process.waitFor(10, TimeUnit.SECONDS)
|
||||
if (!finished) {
|
||||
process.destroy()
|
||||
LogUtil.w(AppConfig.TAG, "RootManager: su probe timed out")
|
||||
return false
|
||||
}
|
||||
val isRoot = process.exitValue() == 0 && output.lineSequence().lastOrNull()?.trim() == "0"
|
||||
LogUtil.i(AppConfig.TAG, "RootManager: root available = $isRoot")
|
||||
isRoot
|
||||
} catch (e: Exception) {
|
||||
LogUtil.w(AppConfig.TAG, "RootManager: no root access (${e.message})")
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
package com.v2ray.ang.root
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Process
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.root.RootProxyManager.TABLE
|
||||
import com.v2ray.ang.root.RootProxyManager.TUN
|
||||
import com.v2ray.ang.root.RootProxyManager.teardown
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.PackageUidResolver
|
||||
import com.v2ray.ang.util.Utils
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Installs and removes the iptables / ip-rule routing that pushes system-wide traffic
|
||||
*
|
||||
* A bundled `hev-socks5-tunnel` binary (run as root) creates a tun device and forwards it to
|
||||
* the in-process core's SOCKS inbound; a mangle MARK chain plus a dedicated routing table /
|
||||
* ip rule steer all traffic into the tun. Full TCP + UDP.
|
||||
*
|
||||
* All rules live in dedicated chains ([AppConfig.ROOT_IPTABLES_CHAIN] in the mangle
|
||||
* table, [AppConfig.ROOT_FWD_CHAIN] for LAN sharing) plus a dedicated routing table, so
|
||||
* [teardown] is a clean, bounded flush. Teardown runs before every setup (to clear stale
|
||||
* rules) and on every stop path — leaving rules behind after the core dies would break
|
||||
* the device's connectivity.
|
||||
*/
|
||||
object RootProxyManager {
|
||||
|
||||
private const val CHAIN = AppConfig.ROOT_IPTABLES_CHAIN
|
||||
private const val TUN = AppConfig.ROOT_TUN_NAME
|
||||
private const val TABLE = AppConfig.ROOT_ROUTE_TABLE
|
||||
private const val PRIORITY = AppConfig.ROOT_RULE_PRIORITY
|
||||
private const val FWMARK = AppConfig.ROOT_FWMARK
|
||||
private const val MARK = AppConfig.ROOT_MARK_ROUTE
|
||||
|
||||
// Local / private / multicast destinations that must never be proxied.
|
||||
private val bypassCidrs = listOf(
|
||||
"0.0.0.0/8", "10.0.0.0/8", "127.0.0.0/8", "169.254.0.0/16",
|
||||
"172.16.0.0/12", "192.168.0.0/16", "224.0.0.0/4", "240.0.0.0/4"
|
||||
)
|
||||
|
||||
// IPv6 equivalents (loopback, link-local, ULA/private, multicast). Feeding the v4 list
|
||||
// above to ip6tables silently fails, so the v6 chain needs its own.
|
||||
private val bypassCidrsV6 = listOf(
|
||||
"::1/128", "fe80::/10", "fc00::/7", "ff00::/8"
|
||||
)
|
||||
|
||||
fun start(context: Context): Boolean {
|
||||
teardown(context)
|
||||
val script = buildTun2socksSetup(context) ?: return false
|
||||
val result = RootShell.runScript(context, "setup_rules.sh", script)
|
||||
if (!result.success) {
|
||||
LogUtil.e(AppConfig.TAG, "RootProxyManager: setup failed, rolling back:\n${result.output}")
|
||||
teardown(context)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up LAN/tethering sharing while the device itself uses another mode (e.g. VPN
|
||||
* mode). Runs a dedicated client tun2socks into the in-process core's SOCKS inbound
|
||||
* and forwards tethered clients into it, WITHOUT capturing the device's own traffic
|
||||
* (that keeps flowing through the VpnService). Requires root.
|
||||
*/
|
||||
fun startClientSharing(context: Context): Boolean {
|
||||
teardown(context)
|
||||
val script = buildTun2socksSetup(context, captureDeviceTraffic = false, forceLanShare = true)
|
||||
?: return false
|
||||
val result = RootShell.runScript(context, "setup_rules.sh", script)
|
||||
if (!result.success) {
|
||||
LogUtil.e(AppConfig.TAG, "RootProxyManager: client sharing setup failed:\n${result.output}")
|
||||
teardown(context)
|
||||
return false
|
||||
}
|
||||
LogUtil.i(AppConfig.TAG, "RootProxyManager: LAN client sharing installed")
|
||||
return true
|
||||
}
|
||||
|
||||
/** Remove all rules and stop helper processes. Safe to call repeatedly. */
|
||||
fun stop(context: Context) {
|
||||
teardown(context)
|
||||
LogUtil.i(AppConfig.TAG, "RootProxyManager: rules removed")
|
||||
}
|
||||
|
||||
private fun teardown(context: Context) {
|
||||
RootShell.runScript(context, "teardown_rules.sh", buildTeardown(context))
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------- TUN2SOCKS
|
||||
|
||||
/**
|
||||
* @param captureDeviceTraffic when true (Root mode) the device's own OUTPUT traffic is
|
||||
* marked into the tun. When false (VPN-mode LAN sharing) the device keeps using the
|
||||
* VpnService and only forwarded clients are routed into this tun.
|
||||
* @param forceLanShare force the LAN/tethering forward rules on regardless of the pref
|
||||
* (used by VPN-mode sharing, where the whole point is forwarding clients).
|
||||
*/
|
||||
private fun buildTun2socksSetup(
|
||||
context: Context,
|
||||
captureDeviceTraffic: Boolean = true,
|
||||
forceLanShare: Boolean = false,
|
||||
): String? {
|
||||
val bin = File(context.applicationInfo.nativeLibraryDir, AppConfig.ROOT_TUN2SOCKS_BIN)
|
||||
if (!bin.exists()) {
|
||||
LogUtil.e(AppConfig.TAG, "RootProxyManager: hev-socks5-tunnel binary missing at ${bin.absolutePath}")
|
||||
return null
|
||||
}
|
||||
val appUid = context.applicationInfo.uid
|
||||
val port = SettingsManager.getSocksPort()
|
||||
val runDir = File(context.filesDir, AppConfig.ROOT_RUNTIME_DIR).apply { mkdirs() }
|
||||
val pidFile = File(runDir, "tun2socks.pid").absolutePath
|
||||
val logFile = File(runDir, "tun2socks.log").absolutePath
|
||||
val cfgFile = File(runDir, "tun2socks.yml").absolutePath
|
||||
val oomGuardPid = File(runDir, "oomguard.pid").absolutePath
|
||||
val ipv6 = MmkvManager.decodeSettingsBool(AppConfig.PREF_IPV6_ENABLED)
|
||||
val lanShare = forceLanShare || MmkvManager.decodeSettingsBool(AppConfig.PREF_ROOT_LAN_SHARING)
|
||||
val corePid = Process.myPid()
|
||||
|
||||
// Per-app proxy/bypass (mirrors what VpnService does via allowed/disallowed apps).
|
||||
val perAppEnabled = MmkvManager.decodeSettingsBool(AppConfig.PREF_PER_APP_PROXY)
|
||||
val bypassApps = MmkvManager.decodeSettingsBool(AppConfig.PREF_BYPASS_APPS)
|
||||
val selectedUids = if (perAppEnabled) {
|
||||
val pkgs = MmkvManager.decodeSettingsStringSet(AppConfig.PREF_PER_APP_PROXY_SET)?.toList().orEmpty()
|
||||
if (pkgs.isNotEmpty()) PackageUidResolver.packageNamesToUids(context, pkgs) else emptyList()
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
return buildString {
|
||||
appendLine("set -e")
|
||||
appendLine("BIN='${bin.absolutePath}'")
|
||||
// Protect the core (this app process) from the Android low-memory killer.
|
||||
// system_server keeps recomputing oom_score_adj for app processes, so a single
|
||||
// write would be reverted — re-pin it from a small root loop instead.
|
||||
appendLine("nohup sh -c 'while true; do echo ${AppConfig.ROOT_OOM_SCORE} > /proc/$corePid/oom_score_adj 2>/dev/null; sleep 5; done' >/dev/null 2>&1 &")
|
||||
appendLine("echo \$! > '$oomGuardPid'")
|
||||
// tun device node
|
||||
appendLine("if [ ! -e /dev/net/tun ]; then mkdir -p /dev/net; mknod /dev/net/tun c 10 200; chmod 666 /dev/net/tun; fi")
|
||||
// hev-socks5-tunnel config: it creates the tun ($TUN) itself and forwards it to the
|
||||
// in-process core's SOCKS inbound on loopback. MTU comes from the existing VPN MTU
|
||||
// setting. No fwmark on hev's sockets: its only upstream connection is to 127.0.0.1
|
||||
// (loopback, already RETURNed by the 127.0.0.0/8 bypass) and the core's real outbound
|
||||
// runs as the app uid (RETURNed by the uid-owner rule), so traffic can't loop.
|
||||
appendLine("cat > '$cfgFile' <<'HEVCFG'")
|
||||
append(buildHevConfig(port, ipv6))
|
||||
appendLine("HEVCFG")
|
||||
appendLine("nohup \"\$BIN\" '$cfgFile' >'$logFile' 2>&1 &")
|
||||
appendLine("T2S_PID=\$!")
|
||||
appendLine("echo \$T2S_PID > '$pidFile'")
|
||||
appendLine("echo ${AppConfig.ROOT_OOM_SCORE} > /proc/\$T2S_PID/oom_score_adj 2>/dev/null || true")
|
||||
// wait for the interface hev creates to appear
|
||||
appendLine("i=0; while [ \$i -lt 20 ]; do ip link show $TUN >/dev/null 2>&1 && break; sleep 0.3; i=\$((i+1)); done")
|
||||
appendLine("ip link show $TUN >/dev/null 2>&1 || { echo 'tun device did not come up'; cat '$logFile' 2>/dev/null; exit 1; }")
|
||||
// relax reverse-path filtering for the tun
|
||||
appendLine("echo 0 > /proc/sys/net/ipv4/conf/$TUN/rp_filter 2>/dev/null || true")
|
||||
appendLine("echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter 2>/dev/null || true")
|
||||
// address + default route in a dedicated table
|
||||
appendLine("ip addr add ${AppConfig.ROOT_TUN_ADDR_V4} dev $TUN 2>/dev/null || true")
|
||||
appendLine("ip link set dev $TUN up")
|
||||
appendLine("ip route replace default dev $TUN table $TABLE")
|
||||
appendLine("ip rule add fwmark $MARK table $TABLE priority $PRIORITY")
|
||||
// mark the device's own packets into the tun (Root mode only)
|
||||
if (captureDeviceTraffic) {
|
||||
append(buildMangleMarking("iptables", appUid, perAppEnabled, bypassApps, selectedUids))
|
||||
}
|
||||
// optionally route hotspot / USB-tethered clients through the tun too
|
||||
if (lanShare) {
|
||||
append(buildLanShareSetup(captureDeviceTraffic, ipv6))
|
||||
}
|
||||
if (captureDeviceTraffic) {
|
||||
// IPv6 is best-effort: never fail the (working) IPv4 setup over it.
|
||||
appendLine("set +e")
|
||||
if (ipv6) {
|
||||
// route the device's v6 into the tun, same as v4
|
||||
appendLine("ip -6 addr add ${AppConfig.ROOT_TUN_ADDR_V6} dev $TUN 2>/dev/null || true")
|
||||
appendLine("ip -6 route replace default dev $TUN table $TABLE 2>/dev/null || true")
|
||||
appendLine("ip -6 rule add fwmark $MARK table $TABLE priority $PRIORITY 2>/dev/null || true")
|
||||
append(buildMangleMarking("ip6tables", appUid, perAppEnabled, bypassApps, selectedUids))
|
||||
} else {
|
||||
// v6 disabled: blackhole native v6 egress for the captured apps so they
|
||||
// fall back to v4-through-proxy, matching what a v4-only VpnService does.
|
||||
append(buildV6Blackhole(appUid, perAppEnabled, bypassApps, selectedUids))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* hev-socks5-tunnel YAML config. hev creates the tun device named [TUN] itself, assigns it
|
||||
* the tun addresses, and forwards everything it receives to the core's SOCKS inbound on
|
||||
* loopback (TCP + UDP). MTU is taken from the existing VPN MTU setting. v6 is only given a
|
||||
* tun address when IPv6 is enabled; whether v6 actually flows in is decided separately by
|
||||
* the v6 route into [TABLE].
|
||||
*/
|
||||
private fun buildHevConfig(socksPort: Int, ipv6: Boolean): String {
|
||||
val v4 = AppConfig.ROOT_TUN_ADDR_V4.substringBefore("/")
|
||||
val v6 = AppConfig.ROOT_TUN_ADDR_V6.substringBefore("/")
|
||||
return buildString {
|
||||
appendLine("tunnel:")
|
||||
appendLine(" name: '$TUN'")
|
||||
appendLine(" mtu: ${SettingsManager.getVpnMtu()}")
|
||||
appendLine(" multi-queue: true")
|
||||
appendLine(" ipv4: '$v4'")
|
||||
if (ipv6) appendLine(" ipv6: '$v6'")
|
||||
appendLine("socks5:")
|
||||
appendLine(" port: $socksPort")
|
||||
appendLine(" address: '${AppConfig.LOOPBACK}'")
|
||||
appendLine(" udp: 'udp'")
|
||||
appendLine(" tcp-fastopen: true")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mangle OUTPUT marking chain (ipv4/ipv6). Mirrors VpnService's capture behavior:
|
||||
* - all-apps (no per-app): mark EVERY remaining uid (incl uid 0 + all system uids), so
|
||||
* nothing is missed;
|
||||
* - bypass mode: the selected apps go fully direct, everything else is captured;
|
||||
* - proxy mode: only the selected apps are captured.
|
||||
*/
|
||||
private fun buildMangleMarking(
|
||||
cmd: String,
|
||||
appUid: Int,
|
||||
perAppEnabled: Boolean,
|
||||
bypassApps: Boolean,
|
||||
selectedUids: List<String>,
|
||||
): String {
|
||||
val allowMode = perAppEnabled && !bypassApps
|
||||
val bypassSelected = perAppEnabled && bypassApps && selectedUids.isNotEmpty()
|
||||
return buildString {
|
||||
appendLine("$cmd -t mangle -N $CHAIN 2>/dev/null || true")
|
||||
appendLine("$cmd -t mangle -F $CHAIN")
|
||||
// the app's own core traffic (the real outbound) must not loop back into the tun.
|
||||
// The $FWMARK RETURN is kept defensively (hev itself only talks to loopback, which
|
||||
// the 127.0.0.0/8 bypass below already RETURNs).
|
||||
appendLine("$cmd -t mangle -A $CHAIN -m mark --mark $FWMARK -j RETURN")
|
||||
appendLine("$cmd -t mangle -A $CHAIN -m owner --uid-owner $appUid -j RETURN")
|
||||
// bypass mode: selected apps go fully direct (incl their DNS)
|
||||
if (bypassSelected) {
|
||||
selectedUids.forEach { appendLine("$cmd -t mangle -A $CHAIN -m owner --uid-owner $it -j RETURN") }
|
||||
}
|
||||
// Route DNS through the core for ALL modes, with no uid filter. On Android the
|
||||
// DNS query is sent by netd (a shared system uid) on behalf of the app, not under
|
||||
// the app's own uid, so it can't be attributed to a selected uid via owner-match.
|
||||
// This MUST also run before the LAN-bypass RETURNs below, otherwise a query to a
|
||||
// LAN/router resolver (192.168.x / 10.x) would be returned direct and resolved by
|
||||
// the local ISP resolver (DNS leak + CDN mis-resolution, e.g. Instagram media).
|
||||
// The MARK survives a later RETURN, so the marked query still routes into the tun.
|
||||
appendLine("$cmd -t mangle -A $CHAIN -p udp --dport 53 -j MARK --set-xmark $MARK")
|
||||
appendLine("$cmd -t mangle -A $CHAIN -p tcp --dport 53 -j MARK --set-xmark $MARK")
|
||||
// keep LAN / private destinations direct (per-family CIDR list)
|
||||
val cidrs = if (cmd == "ip6tables") bypassCidrsV6 else bypassCidrs
|
||||
cidrs.forEach { appendLine("$cmd -t mangle -A $CHAIN -d $it -j RETURN") }
|
||||
if (allowMode) {
|
||||
// Proxy ONLY the explicitly selected apps. If nothing resolved (e.g. the
|
||||
// selected packages failed to resolve to uids at early boot), mark nothing
|
||||
// instead of falling through to the catch-all below: a fail-open here would
|
||||
// tunnel every unselected app — both a privacy leak and the "per-app proxies
|
||||
// everything after a reboot" bug.
|
||||
selectedUids.forEach { appendLine("$cmd -t mangle -A $CHAIN -m owner --uid-owner $it -j MARK --set-xmark $MARK") }
|
||||
} else {
|
||||
// all-apps mode (per-app off) or bypass mode: capture EVERY remaining uid
|
||||
// (incl uid 0 + system uids)
|
||||
appendLine("$cmd -t mangle -A $CHAIN -j MARK --set-xmark $MARK")
|
||||
}
|
||||
appendLine("$cmd -t mangle -D OUTPUT -j $CHAIN 2>/dev/null || true")
|
||||
appendLine("$cmd -t mangle -A OUTPUT -j $CHAIN")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Blackhole native IPv6 egress for the captured app population when IPv6 is NOT routed
|
||||
* into the tun. A v4-only VpnService has no v6 route, so the kernel rejects apps' v6 and
|
||||
* they fall back to IPv4; Root mode has to reproduce that explicitly, otherwise v6-capable
|
||||
* apps reach destinations natively, bypassing the proxy / leaking. REJECT (not DROP) gives
|
||||
* an instant failure so happy-eyeballs falls back to v4 without a timeout.
|
||||
*
|
||||
* Exemptions mirror the v4 chain: the tun2socks helper (fwmark), the app's own core (uid),
|
||||
* loopback, link-local / multicast (NDP/RA/MLD) and ULA/LAN destinations. Per-app selection
|
||||
* is honored: in bypass mode the bypassed apps keep native v6; in proxy mode only the
|
||||
* selected apps lose v6 (everything else stays fully direct).
|
||||
*/
|
||||
private fun buildV6Blackhole(
|
||||
appUid: Int,
|
||||
perAppEnabled: Boolean,
|
||||
bypassApps: Boolean,
|
||||
selectedUids: List<String>,
|
||||
): String {
|
||||
val chain = AppConfig.ROOT_V6_CHAIN
|
||||
val allowMode = perAppEnabled && !bypassApps
|
||||
val bypassSelected = perAppEnabled && bypassApps && selectedUids.isNotEmpty()
|
||||
val reject = "-j REJECT --reject-with icmp6-adm-prohibited"
|
||||
return buildString {
|
||||
appendLine("ip6tables -t filter -N $chain 2>/dev/null || true")
|
||||
appendLine("ip6tables -t filter -F $chain")
|
||||
// never touch the helper, the core, loopback, NDP/link-local/multicast or LAN
|
||||
appendLine("ip6tables -t filter -A $chain -m mark --mark $FWMARK -j RETURN")
|
||||
appendLine("ip6tables -t filter -A $chain -m owner --uid-owner $appUid -j RETURN")
|
||||
appendLine("ip6tables -t filter -A $chain -o lo -j RETURN")
|
||||
bypassCidrsV6.forEach { appendLine("ip6tables -t filter -A $chain -d $it -j RETURN") }
|
||||
// bypass mode: bypassed apps keep their native v6
|
||||
if (bypassSelected) {
|
||||
selectedUids.forEach { appendLine("ip6tables -t filter -A $chain -m owner --uid-owner $it -j RETURN") }
|
||||
}
|
||||
if (allowMode) {
|
||||
// proxy mode: only the selected apps lose v6 (so they fall back to v4-via-proxy).
|
||||
// None resolved -> reject nothing, mirroring the v4 chain's fail-closed handling.
|
||||
selectedUids.forEach { appendLine("ip6tables -t filter -A $chain -m owner --uid-owner $it $reject") }
|
||||
} else {
|
||||
// all-apps / bypass: reject everyone left
|
||||
appendLine("ip6tables -t filter -A $chain $reject")
|
||||
}
|
||||
appendLine("ip6tables -t filter -D OUTPUT -j $chain 2>/dev/null || true")
|
||||
appendLine("ip6tables -t filter -A OUTPUT -j $chain")
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------- LAN / tethering sharing
|
||||
|
||||
/**
|
||||
* Route Wi-Fi-hotspot / USB-tethered clients through the tun as well (ipv4).
|
||||
* Best-effort: wrapped in `set +e` so a failure here never breaks the working proxy.
|
||||
* Mirrors Magic_V2Ray's hotspot rules (FORWARD accept, DNS DNAT, source-based policy
|
||||
* routing for private client ranges, MSS clamp).
|
||||
*/
|
||||
private fun buildLanShareSetup(captureDeviceTraffic: Boolean, ipv6: Boolean): String {
|
||||
val fwd = AppConfig.ROOT_FWD_CHAIN
|
||||
val dnsChain = AppConfig.ROOT_DNS_CHAIN
|
||||
val v6fwd = AppConfig.ROOT_V6_FWD_CHAIN
|
||||
val v6pre = AppConfig.ROOT_V6_PRE_CHAIN
|
||||
// Use the app's configured remote DNS (first plain IPv4) as the DNAT target for
|
||||
// tethered clients; fall back to the default when it's a DoH/DoT/IPv6 value that
|
||||
// can't be a DNAT target.
|
||||
val dns = SettingsManager.getRemoteDnsServers()
|
||||
.firstOrNull { Utils.isPureIpAddress(it) && !it.contains(":") }
|
||||
?: AppConfig.ROOT_LAN_DNS
|
||||
val lanCidrs = listOf("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16")
|
||||
return buildString {
|
||||
appendLine("set +e")
|
||||
appendLine("echo 1 > /proc/sys/net/ipv4/ip_forward 2>/dev/null || true")
|
||||
// forward traffic to/from the tun
|
||||
appendLine("iptables -N $fwd 2>/dev/null || true")
|
||||
appendLine("iptables -F $fwd")
|
||||
appendLine("iptables -A $fwd -i $TUN -j ACCEPT")
|
||||
appendLine("iptables -A $fwd -o $TUN -j ACCEPT")
|
||||
appendLine("iptables -D FORWARD -j $fwd 2>/dev/null || true")
|
||||
appendLine("iptables -I FORWARD -j $fwd")
|
||||
// clamp MSS to avoid TLS fragmentation overhead through the tunnel
|
||||
appendLine("iptables -t mangle -D FORWARD -o $TUN -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350 2>/dev/null || true")
|
||||
appendLine("iptables -t mangle -A FORWARD -o $TUN -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350")
|
||||
// hijack tethered clients' DNS into a dedicated chain so it resolves through the
|
||||
// tunnel; the chain keeps teardown independent of the resolver IP
|
||||
appendLine("iptables -t nat -N $dnsChain 2>/dev/null || true")
|
||||
appendLine("iptables -t nat -F $dnsChain")
|
||||
lanCidrs.forEach {
|
||||
appendLine("iptables -t nat -A $dnsChain ! -i $TUN -d $it -p udp --dport 53 -j DNAT --to $dns")
|
||||
}
|
||||
appendLine("iptables -t nat -D PREROUTING -j $dnsChain 2>/dev/null || true")
|
||||
appendLine("iptables -t nat -A PREROUTING -j $dnsChain")
|
||||
// policy routing: return-path via main, LAN direct, the rest via the tun table
|
||||
appendLine("ip rule add iif lo goto 6000 pref 5000 2>/dev/null || true")
|
||||
appendLine("ip rule add iif $TUN lookup main suppress_prefixlength 0 pref 5010 2>/dev/null || true")
|
||||
appendLine("ip rule add iif $TUN goto 6000 pref 5020 2>/dev/null || true")
|
||||
appendLine("ip rule add to 10.0.0.0/8 lookup main pref 5025 2>/dev/null || true")
|
||||
appendLine("ip rule add to 172.16.0.0/12 lookup main pref 5026 2>/dev/null || true")
|
||||
appendLine("ip rule add to 192.168.0.0/16 lookup main pref 5027 2>/dev/null || true")
|
||||
appendLine("ip rule add from 10.0.0.0/8 lookup $TABLE pref 5030 2>/dev/null || true")
|
||||
appendLine("ip rule add from 172.16.0.0/12 lookup $TABLE pref 5040 2>/dev/null || true")
|
||||
appendLine("ip rule add from 192.168.0.0/16 lookup $TABLE pref 5050 2>/dev/null || true")
|
||||
appendLine("ip rule add nop pref 6000 2>/dev/null || true")
|
||||
|
||||
// ---------------------------------------------------------- IPv6 clients
|
||||
// Tethered/hotspot clients get a native (RA-assigned) global IPv6. The IPv4 rules
|
||||
// above don't touch it, so it egresses the upstream interface directly, bypassing
|
||||
// the proxy = IPv6 leak. Handle it explicitly (mirrors vincentng295/Magic_V2Ray
|
||||
// cae4f7f): route it through the tun when v6 is enabled, reject it when it isn't.
|
||||
appendLine("ip6tables -N $v6fwd 2>/dev/null || true")
|
||||
appendLine("ip6tables -F $v6fwd")
|
||||
appendLine("ip6tables -D FORWARD -j $v6fwd 2>/dev/null || true")
|
||||
appendLine("ip6tables -I FORWARD -j $v6fwd")
|
||||
if (ipv6) {
|
||||
// When the device itself isn't capturing v6 (VPN-mode sharing) the tun table
|
||||
// has no v6 default and the tun has no v6 address — add them so marked client
|
||||
// v6 has somewhere to go. In Root mode the device-capture block already did.
|
||||
if (!captureDeviceTraffic) {
|
||||
appendLine("ip -6 addr add ${AppConfig.ROOT_TUN_ADDR_V6} dev $TUN 2>/dev/null || true")
|
||||
appendLine("ip -6 route replace default dev $TUN table $TABLE 2>/dev/null || true")
|
||||
appendLine("ip -6 rule add fwmark $MARK table $TABLE priority $PRIORITY 2>/dev/null || true")
|
||||
}
|
||||
// allow forwarding to/from the tun
|
||||
appendLine("ip6tables -A $v6fwd -i $TUN -j ACCEPT")
|
||||
appendLine("ip6tables -A $v6fwd -o $TUN -j ACCEPT")
|
||||
// mark forwarded (non-locally-sourced) client v6 into the tun table. DNS first
|
||||
// so a query to a LAN/router resolver is still tunneled (MARK survives RETURN);
|
||||
// keep loopback, link-local (NDP/RA) and ULA/multicast direct.
|
||||
appendLine("ip6tables -t mangle -N $v6pre 2>/dev/null || true")
|
||||
appendLine("ip6tables -t mangle -F $v6pre")
|
||||
appendLine("ip6tables -t mangle -A $v6pre ! -i $TUN -p udp --dport 53 -j MARK --set-xmark $MARK")
|
||||
appendLine("ip6tables -t mangle -A $v6pre ! -i $TUN -p tcp --dport 53 -j MARK --set-xmark $MARK")
|
||||
bypassCidrsV6.forEach { appendLine("ip6tables -t mangle -A $v6pre ! -i $TUN -d $it -j RETURN") }
|
||||
appendLine("ip6tables -t mangle -A $v6pre ! -i $TUN -j MARK --set-xmark $MARK")
|
||||
appendLine("ip6tables -t mangle -D PREROUTING -j $v6pre 2>/dev/null || true")
|
||||
appendLine("ip6tables -t mangle -A PREROUTING -j $v6pre")
|
||||
// fail closed: any forwarded v6 that wasn't marked into the tun (e.g. the
|
||||
// addrtype match is unavailable, or marking failed) is rejected rather than
|
||||
// leaked straight out the upstream interface.
|
||||
appendLine("ip6tables -A $v6fwd -j REJECT --reject-with icmp6-no-route")
|
||||
} else {
|
||||
// v6 disabled: reject forwarded clients' native v6 so it can't leak past the
|
||||
// proxy (the device's own v6 is blackholed separately in OUTPUT).
|
||||
appendLine("ip6tables -A $v6fwd -j REJECT --reject-with icmp6-no-route")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- teardown
|
||||
|
||||
private fun buildTeardown(context: Context): String {
|
||||
val runDir = File(context.filesDir, AppConfig.ROOT_RUNTIME_DIR)
|
||||
val pidFile = File(runDir, "tun2socks.pid").absolutePath
|
||||
val oomGuardPid = File(runDir, "oomguard.pid").absolutePath
|
||||
val corePid = Process.myPid()
|
||||
return buildString {
|
||||
// mangle (TUN2SOCKS), both families
|
||||
for (cmd in listOf("iptables", "ip6tables")) {
|
||||
appendLine("$cmd -t mangle -D OUTPUT -j $CHAIN 2>/dev/null || true")
|
||||
appendLine("$cmd -t mangle -F $CHAIN 2>/dev/null || true")
|
||||
appendLine("$cmd -t mangle -X $CHAIN 2>/dev/null || true")
|
||||
}
|
||||
// IPv6 blackhole chain (only set up when v6 is disabled; harmless if absent)
|
||||
appendLine("ip6tables -t filter -D OUTPUT -j ${AppConfig.ROOT_V6_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -t filter -F ${AppConfig.ROOT_V6_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -t filter -X ${AppConfig.ROOT_V6_CHAIN} 2>/dev/null || true")
|
||||
// routing rule + table
|
||||
appendLine("ip rule del fwmark $MARK table $TABLE priority $PRIORITY 2>/dev/null || true")
|
||||
appendLine("ip -6 rule del fwmark $MARK table $TABLE priority $PRIORITY 2>/dev/null || true")
|
||||
appendLine("ip route flush table $TABLE 2>/dev/null || true")
|
||||
appendLine("ip -6 route flush table $TABLE 2>/dev/null || true")
|
||||
// LAN / tethering sharing (always cleaned, harmless if it was never set up)
|
||||
appendLine("iptables -D FORWARD -j ${AppConfig.ROOT_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("iptables -F ${AppConfig.ROOT_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("iptables -X ${AppConfig.ROOT_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("iptables -t mangle -D FORWARD -o $TUN -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1350 2>/dev/null || true")
|
||||
appendLine("iptables -t nat -D PREROUTING -j ${AppConfig.ROOT_DNS_CHAIN} 2>/dev/null || true")
|
||||
appendLine("iptables -t nat -F ${AppConfig.ROOT_DNS_CHAIN} 2>/dev/null || true")
|
||||
appendLine("iptables -t nat -X ${AppConfig.ROOT_DNS_CHAIN} 2>/dev/null || true")
|
||||
// IPv6 LAN-sharing chains (forward accept/reject + forwarded-client marking)
|
||||
appendLine("ip6tables -D FORWARD -j ${AppConfig.ROOT_V6_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -F ${AppConfig.ROOT_V6_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -X ${AppConfig.ROOT_V6_FWD_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -t mangle -D PREROUTING -j ${AppConfig.ROOT_V6_PRE_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -t mangle -F ${AppConfig.ROOT_V6_PRE_CHAIN} 2>/dev/null || true")
|
||||
appendLine("ip6tables -t mangle -X ${AppConfig.ROOT_V6_PRE_CHAIN} 2>/dev/null || true")
|
||||
for (pref in listOf(5000, 5010, 5020, 5025, 5026, 5027, 5030, 5040, 5050, 6000)) {
|
||||
appendLine("ip rule del pref $pref 2>/dev/null || true")
|
||||
}
|
||||
// tun device down + helper process
|
||||
appendLine("ip link set dev $TUN down 2>/dev/null || true")
|
||||
appendLine("[ -f '$pidFile' ] && kill \$(cat '$pidFile') 2>/dev/null || true")
|
||||
appendLine("rm -f '$pidFile'")
|
||||
// stop the OOM re-pin loop and restore the core process's LMK priority
|
||||
appendLine("[ -f '$oomGuardPid' ] && kill \$(cat '$oomGuardPid') 2>/dev/null || true")
|
||||
appendLine("rm -f '$oomGuardPid'")
|
||||
appendLine("echo 0 > /proc/$corePid/oom_score_adj 2>/dev/null || true")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.v2ray.ang.root
|
||||
|
||||
import android.content.Context
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Minimal root command runner backed by the `su` binary.
|
||||
*
|
||||
* Scripts are written to the app's private root runtime dir and executed with
|
||||
* `su -c sh <file>` so shell quoting stays simple. stderr is merged into stdout to
|
||||
* avoid pipe-buffer deadlocks.
|
||||
*/
|
||||
object RootShell {
|
||||
|
||||
data class Result(val code: Int, val output: String) {
|
||||
val success: Boolean get() = code == 0
|
||||
}
|
||||
|
||||
/** Write [script] to `<filesDir>/root/<name>` and run it as root. */
|
||||
fun runScript(context: Context, name: String, script: String): Result {
|
||||
val dir = File(context.filesDir, AppConfig.ROOT_RUNTIME_DIR).apply { mkdirs() }
|
||||
val file = File(dir, name).apply {
|
||||
writeText(script)
|
||||
setExecutable(true, false)
|
||||
}
|
||||
return exec("sh ${file.absolutePath}")
|
||||
}
|
||||
|
||||
fun exec(command: String, timeoutSeconds: Long = 30): Result {
|
||||
return try {
|
||||
val process = ProcessBuilder("su", "-c", command)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
val output = process.inputStream.bufferedReader().use { it.readText() }
|
||||
val finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS)
|
||||
if (!finished) {
|
||||
process.destroy()
|
||||
LogUtil.e(AppConfig.TAG, "RootShell: timed out: $command")
|
||||
return Result(-1, output)
|
||||
}
|
||||
val result = Result(process.exitValue(), output)
|
||||
if (!result.success) {
|
||||
LogUtil.w(AppConfig.TAG, "RootShell: '$command' exited ${result.code}: ${output.trim()}")
|
||||
}
|
||||
result
|
||||
} catch (e: Exception) {
|
||||
LogUtil.e(AppConfig.TAG, "RootShell: failed to run '$command'", e)
|
||||
Result(-1, e.message ?: e.javaClass.simpleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.v2ray.ang.service
|
||||
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.contracts.ServiceControl
|
||||
import com.v2ray.ang.core.CoreServiceManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.root.RootProxyManager
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.MyContextWrapper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.lang.ref.SoftReference
|
||||
|
||||
/**
|
||||
* Foreground service for the root (system-wide) run modes. Unlike [CoreVpnService] it
|
||||
* does not use Android VpnService — traffic is routed by iptables instead
|
||||
* (see [RootProxyManager]).
|
||||
*
|
||||
* The in-process core is started first (so its listener is up and the foreground
|
||||
* notification is posted promptly), then the root routing rules are installed off the
|
||||
* main thread. On teardown the rules are removed before the core stops.
|
||||
*/
|
||||
class CoreRootService : Service(), ServiceControl {
|
||||
|
||||
private var setupJob: Job? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Root: Service created")
|
||||
CoreServiceManager.serviceControl = SoftReference(this)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
LogUtil.i(AppConfig.TAG, "StartCore-Root: command received")
|
||||
|
||||
// Start the in-process core first (this also posts the foreground notification),
|
||||
// then install the root routing off the main thread.
|
||||
if (!CoreServiceManager.startCoreLoop(null)) {
|
||||
LogUtil.e(AppConfig.TAG, "StartCore-Root: core failed to start")
|
||||
stopService()
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
setupJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
if (!RootProxyManager.start(this@CoreRootService)) {
|
||||
LogUtil.e(AppConfig.TAG, "StartCore-Root: failed to start root mode, stopping")
|
||||
stopService()
|
||||
}
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Wait for any in-flight async setup to finish before tearing down. The rules are
|
||||
// installed off the main thread and can take seconds (the setup script waits for the
|
||||
// tun to appear); if a stop arrives during that window, teardown would run first and
|
||||
// the setup would then re-install the rules + tun pointing at a now-dead core,
|
||||
// blackholing all traffic until the next start/stop cycle clears it.
|
||||
runBlocking { setupJob?.cancelAndJoin() }
|
||||
// Remove routing rules BEFORE stopping the core so traffic is never redirected
|
||||
// to a dead listener. Synchronous on purpose — leaving rules behind breaks the net.
|
||||
RootProxyManager.stop(this)
|
||||
CoreServiceManager.stopCoreLoop()
|
||||
}
|
||||
|
||||
override fun getService(): Service = this
|
||||
|
||||
override fun startService() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun vpnProtect(socket: Int): Boolean = true
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder? = null
|
||||
|
||||
override fun attachBaseContext(newBase: Context?) {
|
||||
val context = newBase?.let {
|
||||
MyContextWrapper.wrap(newBase, SettingsManager.getLocale())
|
||||
}
|
||||
super.attachBaseContext(context)
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.v2ray.ang.core.CoreServiceManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.NotificationManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.root.RootLanSharing
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.MyContextWrapper
|
||||
import com.v2ray.ang.util.Utils
|
||||
@@ -134,6 +135,9 @@ class CoreVpnService : VpnService(), ServiceControl {
|
||||
stopAllService()
|
||||
return
|
||||
}
|
||||
|
||||
// Start LAN sharing if enabled in settings
|
||||
RootLanSharing.startClientSharing(this)
|
||||
}
|
||||
|
||||
override fun stopService() {
|
||||
@@ -362,6 +366,8 @@ class CoreVpnService : VpnService(), ServiceControl {
|
||||
tun2SocksService?.stopTun2Socks()
|
||||
tun2SocksService = null
|
||||
|
||||
RootLanSharing.stopClientSharing(this)
|
||||
|
||||
CoreServiceManager.stopCoreLoop()
|
||||
|
||||
if (isForced) {
|
||||
|
||||
@@ -4,7 +4,12 @@ import android.content.Context
|
||||
import com.v2ray.ang.core.CoreConfigManager
|
||||
import com.v2ray.ang.core.CoreNativeManager
|
||||
import com.v2ray.ang.dto.RealPingEvent
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.extension.isComplexType
|
||||
import com.v2ray.ang.extension.isNotNullEmpty
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -76,6 +81,21 @@ class RealPingWorkerService(
|
||||
|
||||
private fun startRealPing(guid: String): Long {
|
||||
val retFailure = -1L
|
||||
|
||||
val config = MmkvManager.decodeServerConfig(guid) ?: return retFailure
|
||||
if (!config.configType.isComplexType()
|
||||
&& config.configType != EConfigType.HYSTERIA2
|
||||
&& config.server.isNotNullEmpty()
|
||||
&& config.serverPort?.toIntOrNull() != null
|
||||
) {
|
||||
val url = config.server.orEmpty()
|
||||
val port = config.serverPort.orEmpty().toInt()
|
||||
val tcpTime = SpeedtestManager.socketConnectTime(url, port, 1000)
|
||||
if (tcpTime <= -1L) {
|
||||
return retFailure
|
||||
}
|
||||
}
|
||||
|
||||
val configResult = CoreConfigManager.getV2rayConfig4Speedtest(context, guid)
|
||||
if (!configResult.status) {
|
||||
return retFailure
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
@@ -20,7 +21,6 @@ import com.v2ray.ang.viewmodel.LogcatViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.Menu
|
||||
@@ -193,6 +194,11 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
toast(R.string.title_file_chooser)
|
||||
return
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CINNAMON_BUN && MmkvManager.decodeSettingsBool(AppConfig.PREF_PROXY_SHARING)) {
|
||||
checkAndRequestPermission(PermissionType.ACCESS_LOCAL_NETWORK) {}
|
||||
}
|
||||
|
||||
CoreServiceManager.startVService(this)
|
||||
}
|
||||
|
||||
@@ -333,12 +339,6 @@ class MainActivity : HelperBaseActivity(), NavigationView.OnNavigationItemSelect
|
||||
true
|
||||
}
|
||||
|
||||
R.id.ping_all -> {
|
||||
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||
mainViewModel.testAllTcping()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.real_ping_all -> {
|
||||
toast(getString(R.string.connection_test_testing_count, mainViewModel.serversCache.count()))
|
||||
mainViewModel.testAllRealPing()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
package com.v2ray.ang.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.v2ray.ang.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
@@ -9,9 +10,12 @@ import androidx.preference.PreferenceFragmentCompat
|
||||
import com.v2ray.ang.AppConfig
|
||||
import com.v2ray.ang.AppConfig.VPN
|
||||
import com.v2ray.ang.R
|
||||
import com.v2ray.ang.extension.toastError
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.helper.MmkvPreferenceDataStore
|
||||
import com.v2ray.ang.root.RootManager
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -40,8 +44,11 @@ class SettingsActivity : BaseActivity() {
|
||||
private val fragmentPackets by lazy { findPreference<ListPreference>(AppConfig.PREF_FRAGMENT_PACKETS) }
|
||||
private val fragmentLength by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_LENGTH) }
|
||||
private val fragmentInterval by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_INTERVAL) }
|
||||
private val fragmentMaxSplit by lazy { findPreference<EditTextPreference>(AppConfig.PREF_FRAGMENT_MAXSPLIT) }
|
||||
|
||||
private val mode by lazy { findPreference<ListPreference>(AppConfig.PREF_MODE) }
|
||||
private val enableRootMode by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_ROOT_MODE_ENABLE) }
|
||||
private val lanSharing by lazy { findPreference<CheckBoxPreference>(AppConfig.PREF_ROOT_LAN_SHARING) }
|
||||
|
||||
private val hevTunLogLevel by lazy { findPreference<ListPreference>(AppConfig.PREF_HEV_TUNNEL_LOGLEVEL) }
|
||||
private val hevTunRwTimeout by lazy { findPreference<EditTextPreference>(AppConfig.PREF_HEV_TUNNEL_RW_TIMEOUT) }
|
||||
@@ -96,6 +103,7 @@ class SettingsActivity : BaseActivity() {
|
||||
updateMode(valueStr)
|
||||
true
|
||||
}
|
||||
|
||||
mode?.dialogLayoutResource = R.layout.preference_with_help_link
|
||||
|
||||
useHevTun?.setOnPreferenceChangeListener { _, newValue ->
|
||||
@@ -112,6 +120,33 @@ class SettingsActivity : BaseActivity() {
|
||||
updateDynamicSocksPort(newValue as Boolean)
|
||||
true
|
||||
}
|
||||
|
||||
enableRootMode?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true && !RootManager.cachedRoot()) {
|
||||
lifecycleScope.launch {
|
||||
if (checkAndRequestRoot()) {
|
||||
enableRootMode?.isChecked = true
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
lanSharing?.setOnPreferenceChangeListener { _, newValue ->
|
||||
if (newValue == true && !RootManager.cachedRoot()) {
|
||||
lifecycleScope.launch {
|
||||
if (checkAndRequestRoot()) {
|
||||
lanSharing?.isChecked = true
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun initPreferenceSummaries() {
|
||||
@@ -160,6 +195,15 @@ class SettingsActivity : BaseActivity() {
|
||||
preferenceScreen?.let { traverse(it) }
|
||||
}
|
||||
|
||||
private suspend fun checkAndRequestRoot(): Boolean {
|
||||
val hasRoot = RootManager.refresh()
|
||||
if (!isAdded) return false
|
||||
if (!hasRoot) {
|
||||
context?.toastError(R.string.toast_root_required)
|
||||
}
|
||||
return hasRoot
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
updateHevTunSettings(MmkvManager.decodeSettingsBool(AppConfig.PREF_USE_HEV_TUNNEL, true))
|
||||
@@ -243,6 +287,7 @@ class SettingsActivity : BaseActivity() {
|
||||
fragmentPackets?.isEnabled = enabled
|
||||
fragmentLength?.isEnabled = enabled
|
||||
fragmentInterval?.isEnabled = enabled
|
||||
fragmentMaxSplit?.isEnabled = enabled
|
||||
}
|
||||
|
||||
private fun updateDynamicSocksPort(enabled: Boolean) {
|
||||
|
||||
@@ -25,14 +25,10 @@ import com.v2ray.ang.extension.toastSuccess
|
||||
import com.v2ray.ang.handler.AngConfigManager
|
||||
import com.v2ray.ang.handler.MmkvManager
|
||||
import com.v2ray.ang.handler.SettingsManager
|
||||
import com.v2ray.ang.handler.SpeedtestManager
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import com.v2ray.ang.util.MessageUtil
|
||||
import com.v2ray.ang.util.Utils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.Collections
|
||||
@@ -46,7 +42,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val isRunning by lazy { MutableLiveData<Boolean>() }
|
||||
val updateListAction by lazy { MutableLiveData<Int>() }
|
||||
val updateTestResultAction by lazy { MutableLiveData<String>() }
|
||||
private val tcpingTestScope by lazy { CoroutineScope(Dispatchers.IO) }
|
||||
|
||||
/**
|
||||
* Refer to the official documentation for [registerReceiver](https://developer.android.com/reference/androidx/core/content/ContextCompat#registerReceiver(android.content.Context,android.content.BroadcastReceiver,android.content.IntentFilter,int):
|
||||
@@ -64,8 +59,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
*/
|
||||
override fun onCleared() {
|
||||
getApplication<AngApplication>().unregisterReceiver(mMsgReceiver)
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestManager.closeAllTcpSockets()
|
||||
LogUtil.i(AppConfig.TAG, "Main ViewModel is cleared")
|
||||
super.onCleared()
|
||||
}
|
||||
@@ -178,32 +171,6 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
return ret
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the TCP ping for all servers.
|
||||
*/
|
||||
fun testAllTcping() {
|
||||
tcpingTestScope.coroutineContext[Job]?.cancelChildren()
|
||||
SpeedtestManager.closeAllTcpSockets()
|
||||
MmkvManager.clearAllTestDelayResults(serversCache.map { it.guid }.toList())
|
||||
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
item.profile.let { outbound ->
|
||||
val serverAddress = outbound.server
|
||||
val serverPort = outbound.serverPort
|
||||
if (serverAddress != null && serverPort != null) {
|
||||
tcpingTestScope.launch {
|
||||
val testResult = SpeedtestManager.tcping(serverAddress, serverPort.toInt())
|
||||
launch(Dispatchers.Main) {
|
||||
MmkvManager.encodeServerTestDelayMillis(item.guid, testResult)
|
||||
updateListAction.value = getPosition(item.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the real ping for all servers.
|
||||
*/
|
||||
@@ -341,11 +308,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
||||
if (subscriptionId.isEmpty() && keywordFilter.isEmpty()) {
|
||||
MmkvManager.removeAllServer()
|
||||
} else {
|
||||
val serverList = MmkvManager.decodeServerList(subscriptionId)
|
||||
for (guid in serverList) {
|
||||
MmkvManager.removeServer(guid)
|
||||
val serversCopy = serversCache.toList()
|
||||
for (item in serversCopy) {
|
||||
MmkvManager.removeServer(item.guid)
|
||||
}
|
||||
serverList.count()
|
||||
serversCache.toList().count()
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -90,10 +90,6 @@
|
||||
android:icon="@drawable/ic_share_24dp"
|
||||
android:title="@string/title_export_all"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/ping_all"
|
||||
android:title="@string/title_ping_all_server"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/real_ping_all"
|
||||
android:title="@string/title_real_ping_all_server"
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">فاصل التحديث غير صالح. الحد الأدنى هو 15 دقيقة.</string>
|
||||
<string name="title_sub_update">تحديث الاشتراك (أول خطوة)</string>
|
||||
<string name="title_ping_all_server">Tcping لجميع الإعدادات</string>
|
||||
<string name="title_real_ping_all_server"> اختبر جميع الإعدادات (3)</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">الفرز حسب نتائج الاختبار (5)</string>
|
||||
@@ -369,6 +368,7 @@
|
||||
<string name="title_pref_fragment_length">طول الجزء (الحد الأدنى - الحد الأقصى)</string>
|
||||
<string name="title_pref_fragment_interval">فاصل الجزء (الحد الأدنى - الحد الأقصى)</string>
|
||||
<string name="title_pref_fragment_enabled">تفعيل الجزء</string>
|
||||
<string name="title_pref_fragment_maxsplit">ماكس سبليت الجزء</string>
|
||||
|
||||
<string name="update_check_for_update">Check for update</string>
|
||||
<string name="update_already_latest_version">Already on the latest version</string>
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">আপডেটের বিরতি সঠিক নয়। সর্বনিম্ন ১৫ মিনিট।</string>
|
||||
<string name="title_sub_update">সাবস্ক্রিপশন আপডেট</string>
|
||||
<string name="title_ping_all_server">সব কনফিগারেশন TCPing</string>
|
||||
<string name="title_real_ping_all_server">সব কনফিগারেশন প্রকৃত বিলম্ব</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">টেস্ট ফলাফল দ্বারা সাজানো</string>
|
||||
@@ -368,6 +367,7 @@
|
||||
<string name="title_pref_fragment_length">ফ্র্যাগমেন্ট দৈর্ঘ্য (ন্যূনতম-সর্বাধিক)</string>
|
||||
<string name="title_pref_fragment_interval">ফ্র্যাগমেন্ট ইন্টারভ্যাল (ন্যূনতম-সর্বাধিক)</string>
|
||||
<string name="title_pref_fragment_enabled">ফ্র্যাগমেন্ট সক্রিয় করুন</string>
|
||||
<string name="title_pref_fragment_maxsplit">ফ্র্যাগমেন্ট ম্যাক্স স্প্লিট</string>
|
||||
|
||||
<string name="update_check_for_update">Check for update</string>
|
||||
<string name="update_already_latest_version">Already on the latest version</string>
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
<resources>
|
||||
<string name="app_widget_name">آلشت</string>
|
||||
<string name="app_tile_name">آلشت</string>
|
||||
<string name="app_tile_first_use">سی کرت ٱولی و کار بوردن ای ویژیی، ز برنومه سی ازاف کردن سرور استفاڌه کۊنین</string>
|
||||
<string name="app_tile_first_use">سی کرت ٱولی و کار بوردن ای ویژیی، ٱول ز من برنومه ی سرور ازاف کۊنین</string>
|
||||
<string name="navigation_drawer_open">گۊشیڌن نومگه کشاری</string>
|
||||
<string name="navigation_drawer_close">بستن نومگه کشاری</string>
|
||||
<string name="migration_success">مووفقیت من جاگورویی داده</string>
|
||||
<string name="action_stop_service">واڌاشتن خدمات</string>
|
||||
<string name="migration_fail">جاگورویی داده ٱنجوم نگرؽڌ</string>
|
||||
<string name="pull_down_to_refresh">سی وانۊ کردن، بکشینس بلم!</string>
|
||||
<string name="migration_success">جاگورویی داده یل وا مووفقیت ٱنجوم وابی!</string>
|
||||
<string name="action_stop_service">واڌاشتن سرویس</string>
|
||||
<string name="migration_fail">جاگورویی داده یل شکست خرد!</string>
|
||||
<string name="pull_down_to_refresh">سی وانۊ کردن، بلگه ن بلم بکشین!</string>
|
||||
|
||||
<!-- Notifications -->↓
|
||||
<string name="notification_action_stop_v2ray">واڌاشتن</string>
|
||||
<string name="toast_permission_denied">گرؽڌن موجوز مومکن نؽڌ</string>
|
||||
<string name="toast_permission_denied_notification">گرؽڌن موجوز وارسۊوی مومکن نؽڌ</string>
|
||||
<string name="notification_action_more">سی گرؽڌن دووسمندیا قلوه کیلیک کوݩ</string>
|
||||
<string name="toast_services_start">ره وستن خدمات</string>
|
||||
<string name="toast_services_stop">واڌاشتن خدمات</string>
|
||||
<string name="toast_services_success">ره وستن خدمات وا مووفقیت ٱنجوم وابی</string>
|
||||
<string name="toast_services_failure">ره وستن خدمات وا مووفقیت ٱنجوم نوابی</string>
|
||||
<string name="notification_action_more">سی دووسمندیا قلوه کیلیک کوݩ</string>
|
||||
<string name="toast_services_start">ره وستن سرویس</string>
|
||||
<string name="toast_services_stop">واڌاشتن سرویس</string>
|
||||
<string name="toast_services_success">ره وستن سرویس وا مووفقیت ٱنجوم وابی</string>
|
||||
<string name="toast_services_failure">ره وستن سرویس وا مووفقیت ٱنجوم نوابی</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">فایل کانفیگ</string>
|
||||
@@ -31,16 +31,16 @@
|
||||
<string name="menu_item_import_config_local">و من ٱووردن کانفیگ ز مهلی</string>
|
||||
<string name="menu_item_import_config_policy_group">ٱووردن [بونکۊ سیاست]</string>
|
||||
<string name="menu_item_import_config_proxy_chain">ٱووردن [زنجیره پروکسی]</string>
|
||||
<string name="menu_item_import_config_manually_vmess">هؽل دستی[VMess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">هؽل دستی[VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">هؽل دستی[Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">هؽل دستی[SOCKS]</string>
|
||||
<string name="menu_item_import_config_manually_http">هؽل دستی[HTTP]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">هؽل دستی[Trojan]</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">هؽل دستی[Wireguard]</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">هؽل دستی[Hysteria2]</string>
|
||||
<string name="menu_item_import_config_manually_vmess">ٱووردن [VMess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">ٱووردن [VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">ٱووردن [Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">ٱووردن [SOCKS]</string>
|
||||
<string name="menu_item_import_config_manually_http">ٱووردن [HTTP]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">ٱووردن [Trojan]</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">ٱووردن [Wireguard]</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">ٱووردن [Hysteria2]</string>
|
||||
<string name="del_config_comfirm">پاک بۊ؟</string>
|
||||
<string name="del_invalid_config_comfirm">پؽش ز پاک کردن کانفیگ نا موئتبر واجۊری کوݩ! پاک کردن کانفیگن قوۊل اکۊنی؟</string>
|
||||
<string name="del_invalid_config_comfirm">پؽش ز پاک کردن، پینگ بگرین! الن اخۊین پاک کۊنین؟</string>
|
||||
<string name="server_lab_remarks">نیشتنا</string>
|
||||
<string name="server_lab_address">نشۊوی</string>
|
||||
<string name="server_lab_port">پورت</string>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="server_lab_security">ٱمنیت</string>
|
||||
<string name="server_lab_network">شبکه</string>
|
||||
<string name="server_lab_more_function">جاگورو</string>
|
||||
<string name="server_lab_head_type">نوء سر بلگ</string>
|
||||
<string name="server_lab_head_type">نوع هدر</string>
|
||||
<string name="server_lab_mode_type">هالت gRPC</string>
|
||||
<string name="server_lab_request_host">هاست</string>
|
||||
<string name="server_lab_request_host_http">هاست http</string>
|
||||
@@ -66,11 +66,11 @@
|
||||
<string name="server_lab_path_h2">تور H2</string>
|
||||
<string name="server_lab_path_quic">تور QUIC</string>
|
||||
<string name="server_lab_path_kcp">KCP seed</string>
|
||||
<string name="server_lab_path_grpc">نوم خدمات gRPC</string>
|
||||
<string name="server_lab_path_grpc">نوم سرویس gRPC</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint" translatable="false">Fingerprint</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">اجازه نا ٱمن</string>
|
||||
<string name="server_lab_allow_insecure">هشتن SSL نا ٱمن</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">نشۊوی</string>
|
||||
<string name="server_lab_port3">پورت</string>
|
||||
@@ -81,10 +81,10 @@
|
||||
<string name="server_lab_encryption">رزم نگاری</string>
|
||||
<string name="server_lab_flow">جریان</string>
|
||||
<string name="server_lab_public_key">کیلیت پوی وولاتی</string>
|
||||
<string name="server_lab_preshared_key">کیلیت رزم ناهاڌن ازاف (اختیاری)</string>
|
||||
<string name="server_lab_short_id">ShortID</string>
|
||||
<string name="server_lab_preshared_key">کیلیت پؽش اشتراکی (اختیاری)</string>
|
||||
<string name="server_lab_short_id">شناسه کۊتال</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
|
||||
<string name="server_lab_mldsa65_verify">تاییدیه Mldsa65Verify</string>
|
||||
<string name="server_lab_secret_key">کیلیت سیخومی</string>
|
||||
<string name="server_lab_reserved">Reserved(اختیاری، وا کاما (,) ز یک جوڌا ابۊن)</string>
|
||||
<string name="server_lab_local_address">نشۊوی مهلی (اختیاری IPv4/IPv6، وا کاما (,) ز یک جوڌا ابۊن)</string>
|
||||
@@ -119,32 +119,32 @@
|
||||
<string name="server_lab_ech_config_list">نومگه کانفیگ Ech</string>
|
||||
<string name="server_lab_verify_peer_cert_by_name">تئیڌ گوواهی هومتا و ری نوم</string>
|
||||
<string name="server_lab_pinned_ca256">جا کلک گوواهی (SHA-256)</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
<string name="pinned_ca256_action_fetch">گرؽڌن وو پور کردن جا کلک گوواهی</string>
|
||||
<string name="toast_fetch_cert_sha256_success">جا کلک گوواهی وا مووفقیت گرؽڌه وابی</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">گرؽڌن جا کلک گوواهی مووفق نبی</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">لف گیری فایل ٱنجوم نوابی، ز ی برنومه دؽوۉداری فایل هیاری بگرین</string>
|
||||
<string name="menu_item_add_asset">ازاف کردن دارایی</string>
|
||||
<string name="menu_item_add_file">ازاف کردن فایل</string>
|
||||
<string name="menu_item_add_url">ازاف کردن لینگ</string>
|
||||
<string name="menu_item_add_asset">ٱووردن دارایی</string>
|
||||
<string name="menu_item_add_file">ٱووردن فایل</string>
|
||||
<string name="menu_item_add_url">ٱووردن لینگ</string>
|
||||
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
||||
<string name="title_url">نشۊوی اینترنتی</string>
|
||||
<string name="menu_item_download_file">دانلود فایلا</string>
|
||||
<string name="title_user_asset_add_url">نشۊوی اینترنتی دارایین ازاف کۊنین</string>
|
||||
<string name="title_user_asset_add_url">نشۊوی اینترنتی دارایی ن ازاف کۊنین</string>
|
||||
<string name="msg_file_not_found">فایلن نجوست</string>
|
||||
<string name="msg_remark_is_duplicate">نوم ز زیتر بیڌس</string>
|
||||
<string name="msg_remark_is_duplicate">ای نوم ز زیتر بیڌس</string>
|
||||
<string name="asset_geo_files_sources">بونچک فایلا جوقرافیایی (اختیاری)</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">هونی بار ونی بۊ</string>
|
||||
<string name="msg_dialog_progress">هونی بار ونی ابۊ...</string>
|
||||
<string name="menu_item_search">پیتینیڌن</string>
|
||||
<string name="menu_item_select_all">پسند پوی</string>
|
||||
<string name="menu_item_invert_selection">پسند چواسه</string>
|
||||
<string name="msg_enter_keywords">کلمه کلیدی ن بزنین</string>
|
||||
<string name="menu_item_invert_selection">چواسه کردن پسندا</string>
|
||||
<string name="msg_enter_keywords">کلمه یل کلیدی ن بزنین</string>
|
||||
<string name="switch_bypass_apps_mode">هالت دور زیڌن</string>
|
||||
<string name="menu_item_select_proxy_app">پسند خوتکار پروکسی برنومه</string>
|
||||
<string name="msg_downloading_content">موئتوا هونی دانلود ابۊن</string>
|
||||
<string name="menu_item_select_proxy_app">پسند خوتکار برنومه یلی ک پروکسی اخون</string>
|
||||
<string name="msg_downloading_content">موئتوا هونی دانلود ابۊن...</string>
|
||||
<string name="menu_item_export_proxy_app">و در کشیڌن من کلیپ بورد</string>
|
||||
<string name="menu_item_import_proxy_app">و من ٱووردن ز کلیپ بورد</string>
|
||||
<string name="per_app_proxy_settings">سامووا ب تفکیک برنومه</string>
|
||||
@@ -161,10 +161,10 @@
|
||||
<string name="title_pref_is_booted">منپیز خوتکار مجال ره ونی</string>
|
||||
<string name="summary_pref_is_booted">مجال ره وندن، خوساخوس و سرور پسند بیڌه منپیز ابۊ که گاشڌ نا مووفق بۊ.</string>
|
||||
|
||||
<string name="title_pref_auto_remove_invalid_after_test">پاک کردن خوتکار کانفیگ نا موئتبر بئڌ آزمایش</string>
|
||||
<string name="summary_pref_auto_remove_invalid_after_test">نتیجه یل آزمایش گاشڌ دییق نبۊن؛ کانفیگ پاک وابیڌه ن نتری وورگنی.</string>
|
||||
<string name="title_pref_auto_sort_after_test">ترتیب خوتکار بئڌ آزمایش</string>
|
||||
<string name="summary_pref_auto_sort_after_test">نتیجه یل آزمایش گاشڌ دییق نبۊن؛</string>
|
||||
<string name="title_pref_auto_remove_invalid_after_test">پاک کردن خوتکار کانفیگا نا موئتبر بئڌ پینگ</string>
|
||||
<string name="summary_pref_auto_remove_invalid_after_test">نتیجه یل پینگ گاشڌ دییق نبۊن؛ کانفیگا پاک وابیڌه ن نتری وورگنی.</string>
|
||||
<string name="title_pref_auto_sort_after_test">ترتیب خوتکار بئڌ پینگ گرؽڌن</string>
|
||||
<string name="summary_pref_auto_sort_after_test">نتیجه یل پینگ گاشڌ دییق نبۊن.</string>
|
||||
|
||||
<string name="title_mux_settings">سامووا Mux</string>
|
||||
<string name="title_pref_mux_enabled">ره وندن Mux</string>
|
||||
@@ -182,8 +182,8 @@
|
||||
<string name="summary_pref_speed_enabled">نشووݩ داڌن سورعت هیم سکویی من وارسۊویا.\nنماڌ وارسۊوی و ری و کار گرؽڌن کانفیگ آلشت ابۊ.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">ره وندن تجزیه وو تئلیل کتنا (Sniffing)</string>
|
||||
<string name="summary_pref_sniffing_enabled">و کار گرؽڌن تشخیس نوم دامنه (Sniff) من کتنا (پؽش فرز رۊشن)</string>
|
||||
<string name="title_pref_route_only_enabled">ره وندن routeOnly</string>
|
||||
<string name="summary_pref_sniffing_enabled">قپ ریت سی و در کشیڌن نوم دامنه واقعی ز کتنا (پؽش فرز رۊشن هڌ)</string>
|
||||
<string name="title_pref_route_only_enabled">ره وندن تینا تور جوستن routeOnly</string>
|
||||
<string name="summary_pref_route_only_enabled">نوم دامنه sniffed ن تینا سی تور جوستن و کار بگیرین وو نشۊوی موورد نزرن و عونوان نشۊوی IP ووردارین.</string>
|
||||
|
||||
<string name="title_pref_local_dns_enabled">ره وندن DNS مهلی</string>
|
||||
@@ -220,18 +220,18 @@
|
||||
<string name="title_pref_ip_api_url">نشۊوی اینترنتی آزمایش دووسمندیا منپیز هیم سکویی</string>
|
||||
<string name="summary_pref_ip_api_url">نشۊوی اینترنتی</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">هشتن منپیزا ز شبکه مهلی</string>
|
||||
<string name="title_pref_proxy_sharing_enabled">یک رسۊوی پروکسی من شبکه مهلی LAN</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">پوی دسگایل ترن وا نشۊوی IP ایسا، ز ره socks/http و پروکسی منپیز بۊن، تینا من شبکه قابل ائتماد ره ونده بۊ تا ز منپیز قیر موجاز جلو گری بۊ.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">منپیزا ز شبکه مهلی ن موجار کۊنین، موتمعن بۊین ک من ی شبکه قابل ائتماڌ هڌین.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">یک رسۊوی من شبکه مهلی ره وست؛ موتمعن بۊین ک من ی شبکه ٱمن هڌین.</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی مهلی</string>
|
||||
<string name="title_pref_enable_local_proxy">ره وندن پروکسی مهلی</string>
|
||||
<string name="summary_pref_enable_local_proxy">هرسا ره نوسته بۊ، هیچ پروکسی SOCKS من دسرس نؽ، سی دل هیمو ورۊ رسۊوی یل اشتراک وو کارا جۊر یو نترن پروکسی ن و کار گرن.</string>
|
||||
<string name="summary_pref_enable_local_proxy">هرسا ره نوسته بۊ، هیچ پروکسی SOCKS من دسرس نؽ، سی دل هیمو کارایی جۊر ورۊ رسۊوی یل اشتراک نترن پروکسی ن و کار گرن.</string>
|
||||
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
|
||||
<string name="summary_pref_socks_enable_udp">ائراز هۊویت socks5 مجال استفاڌه ز UDP کاملن ایمن نؽ</string>
|
||||
<string name="summary_pref_socks_port">پورت پروکسی مهلی</string>
|
||||
<string name="title_pref_dynamic_socks_port">ره وندن پورت پروکسی مهلی پۊیا</string>
|
||||
<string name="summary_pref_dynamic_socks_port">هر کرت ک و من ٱووڌنی وورکل ابۊ، ی پورت پروکسی مهلی تساڌۊفی وورکل کۊنین</string>
|
||||
<string name="title_pref_dynamic_socks_port">وورکل پورت موتقیر سی پروکسی مهلی</string>
|
||||
<string name="summary_pref_dynamic_socks_port">هر کرت ک و من ٱووڌنی وورکل ابۊ، ی پورت پروکسی مهلی تساڌۊفی وورکل اکونه.</string>
|
||||
<string name="title_pref_socks_username">نوم منتوری پروکسی مهلی (اختیاری)</string>
|
||||
<string name="summary_pref_socks_username">نوم منتوری</string>
|
||||
<string name="title_pref_socks_password">رزم پروکسی مهلی (اختیاری)</string>
|
||||
@@ -243,7 +243,7 @@
|
||||
<string name="title_pref_confirm_remove">قوۊل کردن پاک کردن کانفیگ</string>
|
||||
<string name="summary_pref_confirm_remove">سی پاک وابیڌن فایل کانفیگ، لنگ قوۊل کردن دووارته ز سمت منتور هڌ.</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">زی اسکنن ره ون</string>
|
||||
<string name="title_pref_start_scan_immediate">زی بنه پا اسکن کود QR</string>
|
||||
<string name="summary_pref_start_scan_immediate">شؽواتگرن سی اسکن، زی مجال ره وندن بۊگۊشین، ٱندی ترین کودن اسکن کۊنین یا شؽواتی ن منه نوار ٱوزار پسند کۊنین.</string>
|
||||
|
||||
<string name="title_pref_append_http_proxy">پروکسی HTTP ن و VPN ازاف کۊنین</string>
|
||||
@@ -279,16 +279,22 @@
|
||||
<string name="title_language">زووݩ</string>
|
||||
<string name="title_ui_settings">سامووا رابت منتوری</string>
|
||||
<string name="title_pref_ui_mode_night">سامووا هالت رابت منتوری</string>
|
||||
<string name="title_pref_use_hev_tunnel">ره وندن ویژیی Hev TUN</string>
|
||||
<string name="title_pref_use_hev_tunnel">و کار گرؽڌن ویژیی Hev TUN</string>
|
||||
<string name="summary_pref_use_hev_tunnel">مجالی ک ره ونده بۊ، TUN ایا hev-socks5-tunnel ن و کار اگره؛ ٱر ره نوۊفته بۊ، همو xray-core ن و کار اگره.</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">Hev Tun سئت گوزارشا</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">زمووݩ مندیر بیڌن خوندن/هؽل کردن Hev Tun (سانیه) (پؽش فرز tcp، udp 300،60)</string>
|
||||
<string name="title_mode_settings">سامووا هالت</string>
|
||||
<string name="title_root_mode_enabled">هالت رۊت ره وست (منتورووݩ رۊت)</string>
|
||||
<string name="summary_root_mode_enabled">ره وستن پروکسی شفاف سئت بلم وا امتیازا ریشه. ای هالت، VPN استاندارد سیستوم ٱندروید ن دور ازنه تا موستقیمن پوی ترافیک شبکه لمی ن وا دست بگره. بئڌ ره وندن، هالت معمۊلی ٱسلی، تور جۊری هر برنومه وو سامووا VPN مربۊته کاملن ناموعتبر ابۊن.</string>
|
||||
<string name="toast_root_required">دسرسی رۊت سی ای ویژیی لازوم هڌ</string>
|
||||
<string name="title_root_lan_sharing">یک رسۊوی LAN / تترینگ (منتورووݩ رۊت وابیڌه)</string>
|
||||
<string name="summary_root_lan_sharing">ترافیک هاتاسپات وایفای وو دسگا یل منپیز وابیڌه وا USB (تترینگ) ن ز تور VPN بفشنین.</string>
|
||||
|
||||
<string name="title_logcat">گوزارشا</string>
|
||||
<string name="logcat_copy">لف گیری</string>
|
||||
<string name="logcat_clear">روفتن</string>
|
||||
<string name="logcat_share">یک رسۊوی گوزارش</string>
|
||||
<string name="title_service_restart">ره وندن دووارته خدمات</string>
|
||||
<string name="title_service_restart">ره وندن دووارته سرویس</string>
|
||||
<string name="title_del_all_config">پاک کردن پوی کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_del_duplicate_config">پاک کردن کانفیگا تکراری بونکۊ سکویی</string>
|
||||
<string name="title_del_invalid_config">پاک کردن کانفیگا نا موئتبر بونکۊ سکویی</string>
|
||||
@@ -306,12 +312,11 @@
|
||||
<string name="sub_setting_pre_profile_tip">موتمعن بۊ ک نوم موستعار هڌس وو جۊرس نی</string>
|
||||
<string name="toast_invalid_update_interval">فاسله ورۊ کردن نا موئتبر هڌ. هدقل مقدار 15 دؽقه هڌ.</string>
|
||||
<string name="title_sub_update">ورۊ کردن اشتراک بونکۊ سکویی</string>
|
||||
<string name="title_ping_all_server">Tcping کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_real_ping_all_server">تئخیر واقعی کانفیگا بونکۊ سکویی</string>
|
||||
<string name="title_user_asset_setting">فایلا بونچک جوقرافیایی</string>
|
||||
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل آزمایش</string>
|
||||
<string name="title_sort_by_test_results">ترتیب و ری نتیجه یل پینگ</string>
|
||||
<string name="title_filter_config">فیلتر کردن کانفیگا</string>
|
||||
<string name="filter_config_all">پوی بونکۊ یل کانفیگ</string>
|
||||
<string name="filter_config_all">پوی</string>
|
||||
<string name="title_del_duplicate_config_count">پاک کردن %d کانفیگ تکراری</string>
|
||||
|
||||
<string name="title_del_config_count">پاک کردن %d کانفیگ</string>
|
||||
@@ -324,7 +329,7 @@
|
||||
<string name="toast_fragment_not_available">نتره نما سکویی ن بجۊره</string>
|
||||
<string name="title_locate_selected_config">کانفیگ پسند وابیڌه ن بجۊرین</string>
|
||||
|
||||
<string name="tasker_start_service">ره وندن خدمات</string>
|
||||
<string name="tasker_start_service">ره وندن سرویس</string>
|
||||
<string name="tasker_setting_confirm">قوۊل</string>
|
||||
|
||||
<!-- RoutingSettingActivity -->
|
||||
@@ -334,7 +339,7 @@
|
||||
<string name="routing_settings_save">زفت کردن</string>
|
||||
<string name="routing_settings_delete">روفتن</string>
|
||||
<string name="routing_settings_rule_title">سامووا قانووݩ تور جوستن</string>
|
||||
<string name="routing_settings_add_rule">ازاف کردن قانووݩ</string>
|
||||
<string name="routing_settings_add_rule">ٱووردن قانووݩ</string>
|
||||
<string name="routing_settings_import_predefined_rulesets">و من ٱووردن قانووا</string>
|
||||
<string name="routing_settings_import_rulesets_tip">قانووایی ک هیم سکو هڌسووݩ پاک ابۊن، هنی هم اخۊی پاکسووݩ کۊنی؟</string>
|
||||
<string name="routing_settings_import_rulesets_from_clipboard">و من ٱووردن قانووا ز کلیپ بورد</string>
|
||||
@@ -368,6 +373,7 @@
|
||||
<string name="title_pref_fragment_length">تۊل کتنا فرگمنت (هدقل-هدکسر)</string>
|
||||
<string name="title_pref_fragment_interval">فاسله منجا کتنا فرگمنت (هدقل-هدکسر)</string>
|
||||
<string name="title_pref_fragment_enabled">ره وندن فرگمنت</string>
|
||||
<string name="title_pref_fragment_maxsplit">هدکسر تقسیم فرگمنت</string>
|
||||
|
||||
<string name="update_check_for_update">واجۊری سی ورۊ رسۊوی</string>
|
||||
<string name="update_already_latest_version">سکو نوسخه دیندایی پۊرنیڌه هڌ</string>
|
||||
|
||||
@@ -2,23 +2,23 @@
|
||||
<resources>
|
||||
<string name="app_widget_name">تعویض</string>
|
||||
<string name="app_tile_name">تعویض</string>
|
||||
<string name="app_tile_first_use">برای اولین بار از این ویژگی استفاده میکنید، لطفا از برنامه برای افزودن سرور استفاده کنید</string>
|
||||
<string name="app_tile_first_use">برای اولین استفاده از این قابلیت، لطفاً ابتدا از داخل برنامه یک سرور اضافه کنید</string>
|
||||
<string name="navigation_drawer_open">باز کردن منو کشویی</string>
|
||||
<string name="navigation_drawer_close">بستن منو کشویی</string>
|
||||
<string name="migration_success">موفقیت در انتقال داده</string>
|
||||
<string name="action_stop_service">توقف کردن سرویس</string>
|
||||
<string name="migration_fail">انتقال داده انجام نشد!</string>
|
||||
<string name="pull_down_to_refresh">لطفاً برای تازه کردن، پایین بکشید!</string>
|
||||
<string name="migration_success">انتقال دادهها با موفقیت انجام شد!</string>
|
||||
<string name="action_stop_service">توقف سرویس</string>
|
||||
<string name="migration_fail">انتقال دادهها با شکست مواجه شد!</string>
|
||||
<string name="pull_down_to_refresh">برای بروزرسانی، صفحه را به پایین بکشید!</string>
|
||||
|
||||
<!-- Notifications -->
|
||||
<string name="notification_action_stop_v2ray">توقف</string>
|
||||
<string name="toast_permission_denied">دریافت مجوز امکان پذیر نیست</string>
|
||||
<string name="toast_permission_denied_notification">دریافت مجوز اعلان امکان پذیر نیست</string>
|
||||
<string name="notification_action_more">برای کسب اطلاعات بیشتر کلیک کنید</string>
|
||||
<string name="toast_permission_denied">امکان دریافت مجوز وجود ندارد</string>
|
||||
<string name="toast_permission_denied_notification">امکان دریافت مجوز ارسال اعلان وجود ندارد</string>
|
||||
<string name="notification_action_more">برای اطلاعات بیشتر کلیک کنید</string>
|
||||
<string name="toast_services_start">شروع خدمات</string>
|
||||
<string name="toast_services_stop">توقف خدمات</string>
|
||||
<string name="toast_services_success">شروع خدمات با موفقیت انجام شد</string>
|
||||
<string name="toast_services_failure">شروع خدمات با موفقیت انجام نشد!</string>
|
||||
<string name="toast_services_stop">توقف سرویس</string>
|
||||
<string name="toast_services_success">سرویس با موفقیت شروع شد</string>
|
||||
<string name="toast_services_failure">شروع سرویس با شکست مواجه شد</string>
|
||||
|
||||
<!--ServerActivity-->
|
||||
<string name="title_server">فایل کانفیگ</string>
|
||||
@@ -29,18 +29,18 @@
|
||||
<string name="menu_item_import_config_qrcode">کانفیگ را از QRcode وارد کنید</string>
|
||||
<string name="menu_item_import_config_clipboard">کانفیگ را از کلیپ بورد وارد کنید</string>
|
||||
<string name="menu_item_import_config_local">کانفیگ را از محلی وارد کنید</string>
|
||||
<string name="menu_item_import_config_policy_group">اضافه کردن [گروه خط مشی]</string>
|
||||
<string name="menu_item_import_config_proxy_chain">Add [Proxy chain]</string>
|
||||
<string name="menu_item_import_config_manually_vmess">تایپ دستی[VMESS]</string>
|
||||
<string name="menu_item_import_config_manually_vless">تایپ دستی[VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">تایپ دستی[SHADOWSOCKS]</string>
|
||||
<string name="menu_item_import_config_manually_socks">تایپ دستی[SOCKS]</string>
|
||||
<string name="menu_item_import_config_manually_http">تایپ دستی[HTTP]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">تایپ دستی[TROJAN]</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">تایپ دستی[WIREGUARD]</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">تایپ دستی[HYSTERIA2]</string>
|
||||
<string name="del_config_comfirm">حذف شود؟</string>
|
||||
<string name="del_invalid_config_comfirm">لطفا قبل از حذف کانفیگ نامعتبر بررسی کنید! حذف کانفیگ را تایید می کنید؟</string>
|
||||
<string name="menu_item_import_config_policy_group">افزودن [گروه خط مشی]</string>
|
||||
<string name="menu_item_import_config_proxy_chain">افزودن [زنجیره پروکسی]</string>
|
||||
<string name="menu_item_import_config_manually_vmess">افزودن [VMess]</string>
|
||||
<string name="menu_item_import_config_manually_vless">افزودن [VLESS]</string>
|
||||
<string name="menu_item_import_config_manually_ss">افزودن [Shadowsocks]</string>
|
||||
<string name="menu_item_import_config_manually_socks">افزودن [SOCKS]</string>
|
||||
<string name="menu_item_import_config_manually_http">افزودن [HTTP]</string>
|
||||
<string name="menu_item_import_config_manually_trojan">افزودن [Trojan]</string>
|
||||
<string name="menu_item_import_config_manually_wireguard">افزودن [Wireguard]</string>
|
||||
<string name="menu_item_import_config_manually_hysteria2">افزودن [Hysteria2]</string>
|
||||
<string name="del_config_comfirm">از حذف این کانفیگ اطمینان دارید؟</string>
|
||||
<string name="del_invalid_config_comfirm">لطفاً قبل از حذف، پینگ بگیرید! آیا از حذف اطمینان دارید؟</string>
|
||||
<string name="server_lab_remarks">ملاحظات</string>
|
||||
<string name="server_lab_address">نشانی</string>
|
||||
<string name="server_lab_port">پورت</string>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="server_lab_security">امنیت</string>
|
||||
<string name="server_lab_network">شبکه</string>
|
||||
<string name="server_lab_more_function">انتقال</string>
|
||||
<string name="server_lab_head_type">نوع سربرگ</string>
|
||||
<string name="server_lab_head_type">نوع هدر</string>
|
||||
<string name="server_lab_mode_type">حالت gRPC</string>
|
||||
<string name="server_lab_request_host">هاست</string>
|
||||
<string name="server_lab_request_host_http">هاست HTTP</string>
|
||||
@@ -57,7 +57,7 @@
|
||||
<string name="server_lab_request_host_httpupgrade">هاست HTTPUpgrade</string>
|
||||
<string name="server_lab_request_host_xhttp">هاست XHTTP</string>
|
||||
<string name="server_lab_request_host_h2">هاست H2</string>
|
||||
<string name="server_lab_request_host_quic">QUIC security</string>
|
||||
<string name="server_lab_request_host_quic">QUIC امنیت</string>
|
||||
<string name="server_lab_request_host_grpc">gRPC Authority</string>
|
||||
<string name="server_lab_path">مسیر</string>
|
||||
<string name="server_lab_path_ws">مسیر WS</string>
|
||||
@@ -65,12 +65,12 @@
|
||||
<string name="server_lab_path_xhttp">مسیر XHTTP</string>
|
||||
<string name="server_lab_path_h2">مسیر H2</string>
|
||||
<string name="server_lab_path_quic">مسیر QUIC</string>
|
||||
<string name="server_lab_path_kcp">KCP seed</string>
|
||||
<string name="server_lab_path_grpc">gRPC ServiceName</string>
|
||||
<string name="server_lab_path_kcp">KCP سید</string>
|
||||
<string name="server_lab_path_grpc">gRPC نام سرویس</string>
|
||||
<string name="server_lab_stream_security">TLS</string>
|
||||
<string name="server_lab_stream_fingerprint">اثرانگشت</string>
|
||||
<string name="server_lab_stream_alpn">Alpn</string>
|
||||
<string name="server_lab_allow_insecure">اعطای مجوز ناامن</string>
|
||||
<string name="server_lab_allow_insecure">مجوز SSL ناامن</string>
|
||||
<string name="server_lab_sni">SNI</string>
|
||||
<string name="server_lab_address3">نشانی</string>
|
||||
<string name="server_lab_port3">پورت</string>
|
||||
@@ -81,19 +81,19 @@
|
||||
<string name="server_lab_encryption">رمزنگاری</string>
|
||||
<string name="server_lab_flow">جریان</string>
|
||||
<string name="server_lab_public_key">کلید عمومی</string>
|
||||
<string name="server_lab_preshared_key">کلید رمزگذاری اضافی (اختیاری)</string>
|
||||
<string name="server_lab_short_id">ShortID</string>
|
||||
<string name="server_lab_preshared_key">کلید پیشاشتراکی (اختیاری)</string>
|
||||
<string name="server_lab_short_id">شناسه کوتاه</string>
|
||||
<string name="server_lab_spider_x">SpiderX</string>
|
||||
<string name="server_lab_mldsa65_verify">Mldsa65Verify</string>
|
||||
<string name="server_lab_mldsa65_verify">تاییدیه Mldsa65Verify</string>
|
||||
<string name="server_lab_secret_key">کلید خصوصی</string>
|
||||
<string name="server_lab_reserved">Reserved (اختیاری، جدا شده با کاما)</string>
|
||||
<string name="server_lab_local_address">آدرس محلی (IPv4/IPv6 اختیاری، جدا شده با کاما)</string>
|
||||
<string name="server_lab_local_mtu">MTU (اختیاری، پیشفرض 1420)</string>
|
||||
<string name="server_lab_kcp_mtu">mKCP MTU (اختیاری، پیشفرض هسته 1350)</string>
|
||||
<string name="server_lab_kcp_tti">mKCP TTI (اختیاری، پیشفرض هسته 50)</string>
|
||||
<string name="toast_success">با موفقیت انجام شد</string>
|
||||
<string name="toast_failure">شکست</string>
|
||||
<string name="toast_none_data">هیچ داده ای وجود ندارد</string>
|
||||
<string name="toast_success">موفقیتآمیز</string>
|
||||
<string name="toast_failure">شکست!</string>
|
||||
<string name="toast_none_data">دادهای یافت نشد</string>
|
||||
<string name="toast_incorrect_protocol">پروتکل نادرست</string>
|
||||
<string name="toast_decoding_failed">رمزگشایی انجام نشد</string>
|
||||
<string name="title_file_chooser">انتخاب فایل کانفیگ</string>
|
||||
@@ -101,8 +101,8 @@
|
||||
<string name="server_customize_config">کانفیگ سفارشی</string>
|
||||
<string name="toast_config_file_invalid">کانفیگ معتبر نیست</string>
|
||||
<string name="server_lab_content">محتوا</string>
|
||||
<string name="toast_none_data_clipboard">هیچ داده ای در کلیپ بورد وجود ندارد</string>
|
||||
<string name="toast_invalid_url">نشانی اینترنتی معتبر نیست</string>
|
||||
<string name="toast_none_data_clipboard">دادهای در کلیپبورد وجود ندارد</string>
|
||||
<string name="toast_invalid_url">نشانی اینترنتی نامعتبر است</string>
|
||||
<string name="toast_insecure_url_protocol">لطفاً از آدرس های اشتراک پروتکل HTTP ناامن استفاده نکنید</string>
|
||||
<string name="server_lab_need_inbound">اطمینان حاصل کنید که پورت ورودی با تنظیمات مطابقت دارد</string>
|
||||
<string name="toast_malformed_josn">کانفیگ درست نیست</string>
|
||||
@@ -124,29 +124,29 @@
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">کپی فایل انجام نشد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||
<string name="toast_asset_copy_failed">کپی فایل شکست خورد، لطفا از برنامه مدیریت فایل استفاده کنید</string>
|
||||
<string name="menu_item_add_file">افزودن فایل ها</string>
|
||||
<string name="menu_item_scan_qrcode">اسکن QRcode</string>
|
||||
<string name="title_url">URL</string>
|
||||
<string name="menu_item_download_file">دانلود فایل ها</string>
|
||||
<string name="title_user_asset_add_url">آدرس اینترنتی را اضافه کنید</string>
|
||||
<string name="msg_file_not_found">فایل پیدا نشد</string>
|
||||
<string name="msg_remark_is_duplicate">نام قبلاً وجود دارد</string>
|
||||
<string name="msg_remark_is_duplicate">این نام از قبل وجود دارد</string>
|
||||
<string name="asset_geo_files_sources">منبع فایل های جغرافیایی (اختیاری)</string>
|
||||
|
||||
<!-- PerAppProxyActivity -->
|
||||
<string name="msg_dialog_progress">بارگذاری</string>
|
||||
<string name="msg_dialog_progress">در حال بارگذاری...</string>
|
||||
<string name="menu_item_search">جستجو</string>
|
||||
<string name="menu_item_select_all">انتخاب همه</string>
|
||||
<string name="menu_item_invert_selection">Invert selection</string>
|
||||
<string name="msg_enter_keywords">کلمه ی کلیدی را وارد نمایید</string>
|
||||
<string name="menu_item_invert_selection">معکوس کردن انتخابها</string>
|
||||
<string name="msg_enter_keywords">کلمات کلیدی را وارد کنید</string>
|
||||
<string name="switch_bypass_apps_mode">حالت دور زدن</string>
|
||||
<string name="menu_item_select_proxy_app">انتخاب خودکار پروکسی برنامه</string>
|
||||
<string name="msg_downloading_content">در حال دانلود محتوا</string>
|
||||
<string name="menu_item_select_proxy_app">انتخاب خودکار برنامههای نیازمند پروکسی</string>
|
||||
<string name="msg_downloading_content">در حال دانلود محتوا...</string>
|
||||
<string name="menu_item_export_proxy_app">خروجی گرفتن در کلیپ بورد</string>
|
||||
<string name="menu_item_import_proxy_app">وارد کردن از کلیپ بورد</string>
|
||||
<string name="per_app_proxy_settings">تنظیمات به تفکیک برنامه</string>
|
||||
<string name="per_app_proxy_settings_enable">فعال کردن به تفکیک برنامه</string>
|
||||
<string name="per_app_proxy_settings_enable">فعالسازی به تفکیک برنامه</string>
|
||||
|
||||
<!-- Preferences -->
|
||||
<string name="title_settings">تنظیمات</string>
|
||||
@@ -158,10 +158,10 @@
|
||||
<string name="title_pref_is_booted">اتصال خودکار هنگام راه اندازی</string>
|
||||
<string name="summary_pref_is_booted">هنگام راه اندازی به طور خودکار به سرور انتخابی متصل می شود که ممکن است ناموفق باشد.</string>
|
||||
|
||||
<string name="title_pref_auto_remove_invalid_after_test">Auto delete invalid config after testing</string>
|
||||
<string name="summary_pref_auto_remove_invalid_after_test">Test results may not be accurate; deleted config cannot be recovered.</string>
|
||||
<string name="title_pref_auto_sort_after_test">Auto sort after testing</string>
|
||||
<string name="summary_pref_auto_sort_after_test">Test results may not be accurate;</string>
|
||||
<string name="title_pref_auto_remove_invalid_after_test">حذف خودکار کانفیگهای نامعتبر بعد از پینگ</string>
|
||||
<string name="summary_pref_auto_remove_invalid_after_test">نتایج پینگ ممکن است همیشه دقیق نباشند؛ کانفیگهای حذفشده قابل بازیابی نیستند.</string>
|
||||
<string name="title_pref_auto_sort_after_test">مرتبسازی خودکار بعد از پینگ گرفتن</string>
|
||||
<string name="summary_pref_auto_sort_after_test">نتایج آزمایش ممکن است کاملاً دقیق نباشند.</string>
|
||||
|
||||
<string name="title_mux_settings">تنظیمات MUX</string>
|
||||
<string name="title_pref_mux_enabled">فعال کردن MUX</string>
|
||||
@@ -179,8 +179,8 @@
|
||||
<string name="summary_pref_speed_enabled">نمایش سرعت فعلی در قسمت اعلان. \nآیکون اعلان بر اساس استفاده تغییر میکند.</string>
|
||||
|
||||
<string name="title_pref_sniffing_enabled">فعال کردن تجزیه و تحلیل بسته ها (Sniffing)</string>
|
||||
<string name="summary_pref_sniffing_enabled">استفاده از تشخیص نام دامنه (Sniff) در بسته ها (به طور پیش فرض فعال است)</string>
|
||||
<string name="title_pref_route_only_enabled">فعال کردن دامنه فقط مسیر یابی (RouteOnly)</string>
|
||||
<string name="summary_pref_sniffing_enabled">تلاش برای استخراج نام دامنه واقعی از بستههای ترافیک (بهطور پیشفرض روشن است)</string>
|
||||
<string name="title_pref_route_only_enabled">فعالسازی فقط مسیر یابی (RouteOnly)</string>
|
||||
<string name="summary_pref_route_only_enabled">از نام دامنه (Snnifed) فقط برای مسیریابی استفاده کنید و آدرس مقصد را به عنوان IP ذخیره کنید.</string>
|
||||
|
||||
|
||||
@@ -218,30 +218,30 @@
|
||||
<string name="title_pref_ip_api_url">تست اطلاعات اتصال فعلی url</string>
|
||||
<string name="summary_pref_ip_api_url">Url</string>
|
||||
|
||||
<string name="title_pref_proxy_sharing_enabled">اجازه اتصالات از طریق شبکه محلی</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">سایر دستگاه ها می توانند با استفاده از آدرس آیپی شما برای استفاده از یک پروکسی محلی متصل شوند. فقط در یک شبکه قابل اعتماد برای جلوگیری از اتصالات غیرمجاز استفاده کنید.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">اتصالات از طریق شبکه محلی را مجاز کنید، مطمئن شوید که در یک شبکه قابل اعتماد هستید.</string>
|
||||
<string name="title_pref_proxy_sharing_enabled">اشتراکگذاری پروکسی در شبکه محلی (LAN)</string>
|
||||
<string name="summary_pref_proxy_sharing_enabled">دستگاههای دیگر میتوانند با وارد کردن IP شما، از پروکسیتان استفاده کنند. برای جلوگیری از سوءاستفاده، فقط در شبکههای امن و قابل اعتماد این گزینه را روشن کنید.</string>
|
||||
<string name="toast_warning_pref_proxysharing_short">اشتراکگذاری در شبکه محلی فعال شد؛ مطمئن شوید در یک شبکه امن هستید.</string>
|
||||
|
||||
<string name="title_pref_socks_port">پورت پروکسی محلی</string>
|
||||
<string name="title_pref_enable_local_proxy">فعالسازی پروکسی محلی</string>
|
||||
<string name="summary_pref_enable_local_proxy">When disabled, no SOCKS proxy is available, so subscription updates and similar actions cannot use the proxy</string>
|
||||
<string name="summary_pref_enable_local_proxy">اگر غیرفعال باشد، هیچ پروکسی SOCKS در دسترس نخواهد بود و کارهایی مثل آپدیت اشتراکها نمیتوانند از پروکسی استفاده کنند.</string>
|
||||
<string name="title_pref_socks_enable_udp">SOCKS5 UDP</string>
|
||||
<string name="summary_pref_socks_enable_udp">احراز هویت socks5 هنگام استفاده از UDP کاملاً ایمن نیست</string>
|
||||
<string name="summary_pref_socks_port">پورت پروکسی محلی</string>
|
||||
<string name="title_pref_dynamic_socks_port">Enable dynamic local proxy port</string>
|
||||
<string name="summary_pref_dynamic_socks_port">Generate a random local proxy port each time the inbound is created</string>
|
||||
<string name="title_pref_socks_username">Local proxy username (optional)</string>
|
||||
<string name="summary_pref_socks_username">Username</string>
|
||||
<string name="title_pref_socks_password">Local proxy password (optional)</string>
|
||||
<string name="summary_pref_socks_password">Password</string>
|
||||
<string name="title_pref_dynamic_socks_port">تولید پورت متغیر برای پروکسی محلی</string>
|
||||
<string name="summary_pref_dynamic_socks_port">با هر بار ایجاد یک کانکشن ورودی، یک پورت تصادفی تولید میکند.</string>
|
||||
<string name="title_pref_socks_username">نام کاربری پروکسی محلی (اختیاری)</string>
|
||||
<string name="summary_pref_socks_username">نام کاربری</string>
|
||||
<string name="title_pref_socks_password">رمز عبور پروکسی محلی (اختیاری)</string>
|
||||
<string name="summary_pref_socks_password">رمز عبور</string>
|
||||
|
||||
<string name="title_pref_local_dns_port">پورت DNS محلی</string>
|
||||
<string name="summary_pref_local_dns_port">پورت DNS محلی</string>
|
||||
|
||||
<string name="title_pref_confirm_remove">تایید حذف فایل کانفیگ</string>
|
||||
<string name="summary_pref_confirm_remove">آیا برای حذف فایل کانفیگ نیاز به تایید دوم توسط کاربر است</string>
|
||||
<string name="title_pref_confirm_remove">تایید حذف کانفیگ</string>
|
||||
<string name="summary_pref_confirm_remove">آیا برای حذف کانفیگ نیاز به تایید دوم توسط کاربر است</string>
|
||||
|
||||
<string name="title_pref_start_scan_immediate">فورا اسکن را شروع کن</string>
|
||||
<string name="title_pref_start_scan_immediate">شروع فوری اسکن QR کد</string>
|
||||
<string name="summary_pref_start_scan_immediate">دوربین را برای اسکن بلافاصله در هنگام راه اندازی باز کنید، در غیر این صورت می توانید کد را اسکن کنید یا عکسی را در نوار ابزار انتخاب کنید.</string>
|
||||
|
||||
<string name="title_pref_append_http_proxy">پروکسی HTTP را به VPN اضافه کنید</string>
|
||||
@@ -276,8 +276,8 @@
|
||||
<string name="title_language">زبان</string>
|
||||
<string name="title_ui_settings">تنظیمات رابط کاربری</string>
|
||||
<string name="title_pref_ui_mode_night">تنظیمات حالت رابط کاربری</string>
|
||||
<string name="title_pref_use_hev_tunnel">Enable Hev TUN Feature</string>
|
||||
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use xray-core.</string>
|
||||
<string name="title_pref_use_hev_tunnel">استفاده از قابلیت Hev TUN</string>
|
||||
<string name="summary_pref_use_hev_tunnel">اگر فعال باشد، تونل VPN از hev-socks5-tunnel استفاده میکند؛ در غیر این صورت از xray-core استفاده خواهد شد.</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">سطح گزارشات Hev Tun</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">زمان انتظار خواندن/نوشتن Hev Tun (ثانیه) (پیشفرض tcp,udp 300،60)</string>
|
||||
|
||||
@@ -303,23 +303,22 @@
|
||||
<string name="sub_setting_pre_profile_tip">لطفاً مطمئن شوید که نام مستعار وجود دارد و منحصر به فرد است</string>
|
||||
<string name="toast_invalid_update_interval">بازه بروزرسانی نامعتبر است. حداقل مقدار 15 دقیقه است.</string>
|
||||
<string name="title_sub_update">بهروزرسانی گروه فعلی اشتراک</string>
|
||||
<string name="title_ping_all_server">TCPING کانفیگ های گروه فعلی</string>
|
||||
<string name="title_real_ping_all_server">تاخیر واقعی کانفیگ های گروه فعلی</string>
|
||||
<string name="title_user_asset_setting">فایل های منبع جغرافیایی</string>
|
||||
<string name="title_sort_by_test_results">مرتب سازی بر اساس نتایج آزمایش</string>
|
||||
<string name="title_filter_config">فیلتر کردن کانفیگها</string>
|
||||
<string name="filter_config_all">All</string>
|
||||
<string name="filter_config_all">همه</string>
|
||||
<string name="title_del_duplicate_config_count">حذف %d کانفیگ تکراری</string>
|
||||
|
||||
<string name="title_del_config_count">حذف %d کانفیگ</string>
|
||||
<string name="title_import_config_count">وارد کردن %d کانفیگ</string>
|
||||
<string name="title_export_config_count">صادر کردن %d کانفیگ</string>
|
||||
<string name="title_update_config_count">آپدیت کردن %d کانفیگ</string>
|
||||
<string name="title_update_subscription_result">Updated %1$d configs (%2$d success, %3$d failed, %4$d skipped)</string>
|
||||
<string name="title_update_subscription_no_subscription">No subscriptions</string>
|
||||
<string name="toast_server_not_found_in_group">Selected server not found in current group</string>
|
||||
<string name="toast_fragment_not_available">Unable to locate current view</string>
|
||||
<string name="title_locate_selected_config">Locate the selected config</string>
|
||||
<string name="title_update_subscription_result">بروزرسانی %1$d کانفیگ انجام شد (%2$d موفق، %3$d شکست خورده، %4$d نادیده گرفته شده)</string>
|
||||
<string name="title_update_subscription_no_subscription">هیچ اشتراکی وجود ندارد</string>
|
||||
<string name="toast_server_not_found_in_group">سرور انتخابشده در گروه فعلی پیدا نشد</string>
|
||||
<string name="toast_fragment_not_available">موقعیت صفحه فعلی پیدا نشد</string>
|
||||
<string name="title_locate_selected_config">پیدا کردن کانفیگ متصلشده در لیست</string>
|
||||
|
||||
<string name="tasker_start_service">شروع خدمات</string>
|
||||
<string name="tasker_setting_confirm">تایید</string>
|
||||
@@ -365,6 +364,7 @@
|
||||
<string name="title_pref_fragment_length">طول بسته های فرگمنت (حداقل-حداکثر)</string>
|
||||
<string name="title_pref_fragment_interval">فاصله بین بسته های فرگمنت (حداقل-حداکثر)</string>
|
||||
<string name="title_pref_fragment_enabled">فعال کردن فرگمنت</string>
|
||||
<string name="title_pref_fragment_maxsplit">حداکثر تقسیم فرگمنت</string>
|
||||
|
||||
<string name="update_check_for_update">بررسی به روز رسانی</string>
|
||||
<string name="update_already_latest_version">در حال حاضر آخرین نسخه نصب شده است</string>
|
||||
@@ -390,7 +390,7 @@
|
||||
<string name="title_configuration_share">اشتراک گذاری پیکربندی</string>
|
||||
<string name="title_webdav_config_setting">تنظیمات WebDAV</string>
|
||||
<string name="title_webdav_config_setting_unknown">لطفا ابتدا WebDAV را پیکربندی کنید.</string>
|
||||
<string name="title_webdav_url">WebDAV server URL</string>
|
||||
<string name="title_webdav_url">لینک سرور WebDAV</string>
|
||||
<string name="title_webdav_user">یوزرنیم</string>
|
||||
<string name="title_webdav_pass">پسورد</string>
|
||||
<string name="title_webdav_remote_path">دایرکتوری راه دور (اختیاری)</string>
|
||||
|
||||
@@ -122,9 +122,9 @@
|
||||
<string name="server_lab_browser_dialer">Использовать переадресацию браузера</string>
|
||||
<string name="server_lab_browser_dialer_tip">Поддерживаются только исходящие соединения XHTTP (packet-up) и WS; настройки, связанные с TLS, могут быть проигнорированы или конфликтовать</string>
|
||||
<string name="toast_allow_insecure_deprecated">ВНИМАНИЕ: пропуск проверки сертификата («Разрешать небезопасные соединения») будет отключён в августе 2026 года. Пожалуйста, используйте отпечатки сертификатов как можно скорее. Функция будет недоступна после истечения срока действия.</string>
|
||||
<string name="pinned_ca256_action_fetch">Fetch and fill certificate fingerprint</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Certificate fingerprint fetched successfully</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Failed to fetch certificate fingerprint</string>
|
||||
<string name="pinned_ca256_action_fetch">Получить и заполнить отпечаток сертификата</string>
|
||||
<string name="toast_fetch_cert_sha256_success">Отпечаток сертификата успешно получен</string>
|
||||
<string name="toast_fetch_cert_sha256_failed">Отпечаток сертификата не получен</string>
|
||||
|
||||
<!-- UserAssetActivity -->
|
||||
<string name="toast_asset_copy_failed">Невозможно скопировать файл, используйте файловый менеджер</string>
|
||||
@@ -289,6 +289,12 @@
|
||||
<string name="summary_pref_use_hev_tunnel">Если включено, TUN будет использовать hev-socks5-tunnel; иначе будет использован xray-core</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">Подробность журнала HevTun</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">Ожидание чтения/записи HevTun (секунд, по умолчанию TCP,UDP 300,60)</string>
|
||||
<string name="title_mode_settings">Настройки режима</string>
|
||||
<string name="title_root_mode_enabled">Использовать root-режим</string>
|
||||
<string name="summary_root_mode_enabled">Низкоуровневое прозрачное проксирование с помощью привилегий root. Этот режим обходит стандартный VPN системы Android, чтобы напрямую управлять всем сетевым трафиком. После включения первоначальный обычный режим, маршрутизация отдельных приложений и связанные с ними настройки VPN станут полностью недействительными.</string>
|
||||
<string name="toast_root_required">Для этой функции требуется доступ с правами root</string>
|
||||
<string name="title_root_lan_sharing">Совместное использование LAN/точки доступа (root)</string>
|
||||
<string name="summary_root_lan_sharing">Маршрутизация точек доступа Wi-Fi и устройств, подключённых к USB, через VPN</string>
|
||||
|
||||
<string name="title_logcat">Журнал</string>
|
||||
<string name="logcat_copy">Копировать</string>
|
||||
@@ -312,7 +318,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">Профиль должен быть уникальным</string>
|
||||
<string name="toast_invalid_update_interval">Неправильный интервал обновления. Минимум 15 минут.</string>
|
||||
<string name="title_sub_update">Обновить подписку</string>
|
||||
<string name="title_ping_all_server">Проверить профили</string>
|
||||
<string name="title_real_ping_all_server">Проверить задержку профилей</string>
|
||||
<string name="title_user_asset_setting">Файлы ресурсов</string>
|
||||
<string name="title_sort_by_test_results">Сортировать по результатам теста</string>
|
||||
@@ -374,6 +379,7 @@
|
||||
<string name="title_pref_fragment_length">Длина фрагмента (от — до)</string>
|
||||
<string name="title_pref_fragment_interval">Интервал фрагментов (от — до)</string>
|
||||
<string name="title_pref_fragment_enabled">Использовать фрагментирование</string>
|
||||
<string name="title_pref_fragment_maxsplit">Максимальное число фрагментов</string>
|
||||
|
||||
<string name="update_check_for_update">Проверить обновление</string>
|
||||
<string name="update_already_latest_version">Установлена последняя версия</string>
|
||||
|
||||
@@ -306,7 +306,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The configuration remarks exists and is unique</string>
|
||||
<string name="toast_invalid_update_interval">Khoảng thời gian cập nhật không hợp lệ. Giá trị tối thiểu là 15 phút.</string>
|
||||
<string name="title_sub_update">Cập nhật các gói đăng ký</string>
|
||||
<string name="title_ping_all_server">Ping tất cả máy chủ</string>
|
||||
<string name="title_real_ping_all_server">Kiểm tra HTTP tất cả máy chủ</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">Sắp xếp lại theo lần kiểm tra cuối cùng</string>
|
||||
@@ -370,6 +369,7 @@
|
||||
<string name="title_pref_fragment_length">Fragment Length (min-max)</string>
|
||||
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
|
||||
<string name="title_pref_fragment_enabled">Enable Fragment</string>
|
||||
<string name="title_pref_fragment_maxsplit">Fragment Max Split</string>
|
||||
|
||||
<string name="update_check_for_update">Check for update</string>
|
||||
<string name="update_already_latest_version">Already on the latest version</string>
|
||||
|
||||
@@ -284,6 +284,13 @@
|
||||
<string name="summary_pref_use_hev_tunnel">选择启用后 TUN 将使用 hev-socks5-tunnel 否则使用 xray-core</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">HevTun 日志级别</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">HevTun 读写超时(秒) (tcp,udp 默认 300,60)</string>
|
||||
<string name="title_mode_settings">模式设置</string>
|
||||
<string name="title_root_mode_enabled">启用 Root 模式 (仅限 Root 用户)</string>
|
||||
<string name="summary_root_mode_enabled">通过 Root 权限启用底层透明代理。此模式将不再通过 Android 常规系统 VPN 建立连接,而是直接接管系统底层所有网络流量。开启后,原有的常规模式、应用分流及相关 VPN 设置将直接失效。</string>
|
||||
<string name="toast_root_required">此功能需要 Root 权限</string>
|
||||
<string name="title_root_lan_sharing">局域网 / 热点网络共享 (仅限 Root 用户)</string>
|
||||
<string name="summary_root_lan_sharing">允许通过 Wi-Fi 热点和 USB 共享网络连接的设备走 VPN 代理流量</string>
|
||||
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">复制</string>
|
||||
@@ -307,7 +314,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">请确保配置别名存在并唯一</string>
|
||||
<string name="toast_invalid_update_interval">更新间隔无效,最小值为 15 分钟。</string>
|
||||
<string name="title_sub_update">更新订阅</string>
|
||||
<string name="title_ping_all_server">测试配置 Tcping</string>
|
||||
<string name="title_real_ping_all_server">测试配置真连接</string>
|
||||
<string name="title_user_asset_setting">资源文件</string>
|
||||
<string name="title_sort_by_test_results">按测试结果排序</string>
|
||||
@@ -372,6 +378,7 @@
|
||||
<string name="title_pref_fragment_length">分片包长(最小 - 最大)</string>
|
||||
<string name="title_pref_fragment_interval">分片间隔(最小 - 最大)</string>
|
||||
<string name="title_pref_fragment_enabled">启用分片(Fragment)</string>
|
||||
<string name="title_pref_fragment_maxsplit">分片最大分割数</string>
|
||||
|
||||
<string name="update_check_for_update">检查更新</string>
|
||||
<string name="update_already_latest_version">目前已是最新版本</string>
|
||||
|
||||
@@ -283,6 +283,13 @@
|
||||
<string name="summary_pref_use_hev_tunnel">選擇啟用後 TUN 將使用 hev-socks5-tunnel 否則使用 xray-core</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">HevTun 日誌級別</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">HevTun 讀寫逾時(秒) (tcp,udp 預設 300,60)</string>
|
||||
<string name="title_mode_settings">模式設定</string>
|
||||
<string name="title_root_mode_enabled">啟用 Root 模式 (僅限 Root 使用者)</string>
|
||||
<string name="summary_root_mode_enabled">透過 Root 權限啟用底層透明代理。此模式將不再透過 Android 常規系統 VPN 建立連線,而是直接接管系統底層所有網路流量。開啟後,原有的常规模式、應用分流及相關 VPN 設定將直接失效。</string>
|
||||
<string name="toast_root_required">此功能需要 Root 權限</string>
|
||||
<string name="title_root_lan_sharing">區域網路 / 熱點網路共享 (僅限 Root 使用者)</string>
|
||||
<string name="summary_root_lan_sharing">允許透過 Wi-Fi 熱點和 USB 共享網路連線的裝置走 VPN 代理流量</string>
|
||||
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">複製</string>
|
||||
@@ -306,7 +313,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">请确保設定檔别名存在并唯一</string>
|
||||
<string name="toast_invalid_update_interval">更新間隔無效,最小值為 15 分鐘。</string>
|
||||
<string name="title_sub_update">更新訂閱</string>
|
||||
<string name="title_ping_all_server">偵測設定 Tcping</string>
|
||||
<string name="title_real_ping_all_server">偵測設定真延遲</string>
|
||||
<string name="title_user_asset_setting">資源檔案</string>
|
||||
<string name="title_sort_by_test_results">依偵測結果排序</string>
|
||||
@@ -370,6 +376,7 @@
|
||||
<string name="title_pref_fragment_length">分片包長(最小-最大)</string>
|
||||
<string name="title_pref_fragment_interval">分片間隔(最小-最大)</string>
|
||||
<string name="title_pref_fragment_enabled">啟用分片(Fragment)</string>
|
||||
<string name="title_pref_fragment_maxsplit">分片最大分割數</string>
|
||||
|
||||
<string name="update_check_for_update">檢查更新</string>
|
||||
<string name="update_already_latest_version">当前已是最新版本</string>
|
||||
|
||||
@@ -291,6 +291,12 @@
|
||||
<string name="summary_pref_use_hev_tunnel">When enabled, TUN will use hev-socks5-tunnel; otherwise, it will use xray-core.</string>
|
||||
<string name="title_pref_hev_tunnel_loglevel">Hev Tun Log Level</string>
|
||||
<string name="title_pref_hev_tunnel_rw_timeout">Hev Tun read/write timeout (seconds) (tcp,udp default 300,60)</string>
|
||||
<string name="title_mode_settings">Mode Settings</string>
|
||||
<string name="title_root_mode_enabled">Root mode enabled (Root Users)</string>
|
||||
<string name="summary_root_mode_enabled">Enable low-level transparent proxying via root privileges. This mode bypasses the standard Android system VPN to directly take over all underlying network traffic. Once enabled, the original regular mode, per-app routing, and related VPN settings will become completely invalid.</string>
|
||||
<string name="toast_root_required">Root access is required for this feature</string>
|
||||
<string name="title_root_lan_sharing">LAN / tethering sharing (Root Users)</string>
|
||||
<string name="summary_root_lan_sharing">Route Wi-Fi hotspot and USB-tethered devices through the VPN</string>
|
||||
|
||||
<string name="title_logcat">Logcat</string>
|
||||
<string name="logcat_copy">Copy</string>
|
||||
@@ -314,7 +320,6 @@
|
||||
<string name="sub_setting_pre_profile_tip">The config remarks exist and are unique</string>
|
||||
<string name="toast_invalid_update_interval">Invalid update interval. Minimum is 15 minutes.</string>
|
||||
<string name="title_sub_update">Update subscription</string>
|
||||
<string name="title_ping_all_server">Tcping config</string>
|
||||
<string name="title_real_ping_all_server">Real delay config</string>
|
||||
<string name="title_user_asset_setting">Asset files</string>
|
||||
<string name="title_sort_by_test_results">Sorting by test results</string>
|
||||
@@ -377,6 +382,7 @@
|
||||
<string name="title_pref_fragment_length">Fragment Length (min-max)</string>
|
||||
<string name="title_pref_fragment_interval">Fragment Interval (min-max)</string>
|
||||
<string name="title_pref_fragment_enabled">Enable Fragment</string>
|
||||
<string name="title_pref_fragment_maxsplit">Fragment Max Split</string>
|
||||
|
||||
<string name="update_check_for_update">Check for update</string>
|
||||
<string name="update_already_latest_version">Already on the latest version</string>
|
||||
|
||||
@@ -264,6 +264,11 @@
|
||||
android:summary="10-20"
|
||||
android:title="@string/title_pref_fragment_interval" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_fragment_maxsplit"
|
||||
android:summary="10"
|
||||
android:title="@string/title_pref_fragment_maxsplit" />
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="tlshello"
|
||||
android:entries="@array/fragment_packets"
|
||||
@@ -309,6 +314,10 @@
|
||||
android:summary="@string/summary_pref_ip_api_url"
|
||||
android:title="@string/title_pref_ip_api_url" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/title_mode_settings">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="VPN"
|
||||
android:entries="@array/mode_entries"
|
||||
@@ -317,6 +326,19 @@
|
||||
android:summary="%s"
|
||||
android:title="@string/title_mode" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_root_mode_enabled"
|
||||
android:summary="@string/summary_root_mode_enabled"
|
||||
android:title="@string/title_root_mode_enabled" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="pref_root_lan_sharing"
|
||||
android:summary="@string/summary_root_lan_sharing"
|
||||
android:title="@string/title_root_lan_sharing" />
|
||||
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.v2ray.ang.fmt
|
||||
|
||||
import android.util.Base64
|
||||
import com.v2ray.ang.util.LogUtil
|
||||
import android.util.Log
|
||||
import com.v2ray.ang.enums.EConfigType
|
||||
import com.v2ray.ang.dto.entities.ProfileItem
|
||||
import org.junit.After
|
||||
|
||||
+66
-4
@@ -16,26 +16,88 @@ clear_tmp () {
|
||||
}
|
||||
trap 'echo -e "Aborted, error $? in command: $BASH_COMMAND"; trap ERR; clear_tmp; exit 1' ERR INT
|
||||
|
||||
#build hev-socks5-tunnel
|
||||
ABIS="armeabi-v7a arm64-v8a x86 x86_64"
|
||||
|
||||
mkdir -p "$TMPDIR/jni"
|
||||
pushd "$TMPDIR"
|
||||
|
||||
echo 'include $(call all-subdir-makefiles)' > jni/Android.mk
|
||||
|
||||
ln -s "$__dir/hev-socks5-tunnel" jni/hev-socks5-tunnel
|
||||
|
||||
# 1) JNI shared library (libhev-socks5-tunnel.so) — loaded in-process by
|
||||
# com.v2ray.ang.service.TProxyService for the VpnService hev tun mode.
|
||||
echo 'include $(call all-subdir-makefiles)' > jni/Android.mk
|
||||
|
||||
"$NDK_HOME/ndk-build" \
|
||||
NDK_PROJECT_PATH=. \
|
||||
APP_BUILD_SCRIPT=jni/Android.mk \
|
||||
"APP_ABI=armeabi-v7a arm64-v8a x86 x86_64" \
|
||||
"APP_ABI=$ABIS" \
|
||||
APP_PLATFORM=android-24 \
|
||||
NDK_LIBS_OUT="$TMPDIR/libs" \
|
||||
NDK_OUT="$TMPDIR/obj" \
|
||||
"APP_CFLAGS=-O3 -DPKGNAME=com/v2ray/ang/service" \
|
||||
"APP_LDFLAGS=-Wl,--build-id=none -Wl,--hash-style=gnu" \
|
||||
|
||||
# 2) Standalone executable (libhevsockstun.so) — run as a separate root
|
||||
# process by com.v2ray.ang.core.root for the Root run mode. Same hev source,
|
||||
# no -DENABLE_LIBRARY so hev-main.c's main() is built, and BUILD_EXECUTABLE
|
||||
# instead of a shared library. It creates its own tun and reads a YAML config.
|
||||
cat > jni/exec.mk <<'EXECMK'
|
||||
TOP_PATH := $(call my-dir)/hev-socks5-tunnel
|
||||
|
||||
ifeq ($(filter $(modules-get-list),yaml),)
|
||||
include $(TOP_PATH)/third-part/yaml/Android.mk
|
||||
endif
|
||||
ifeq ($(filter $(modules-get-list),lwip),)
|
||||
include $(TOP_PATH)/third-part/lwip/Android.mk
|
||||
endif
|
||||
ifeq ($(filter $(modules-get-list),hev-task-system),)
|
||||
include $(TOP_PATH)/third-part/hev-task-system/Android.mk
|
||||
endif
|
||||
|
||||
LOCAL_PATH := $(TOP_PATH)
|
||||
SRCDIR := $(LOCAL_PATH)/src
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
include $(LOCAL_PATH)/build.mk
|
||||
LOCAL_MODULE := hevsockstun
|
||||
LOCAL_SRC_FILES := $(patsubst $(SRCDIR)/%,src/%,$(SRCFILES))
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/src \
|
||||
$(LOCAL_PATH)/src/misc \
|
||||
$(LOCAL_PATH)/src/core/include \
|
||||
$(LOCAL_PATH)/third-part/yaml/include \
|
||||
$(LOCAL_PATH)/third-part/lwip/src/include \
|
||||
$(LOCAL_PATH)/third-part/lwip/src/ports/include \
|
||||
$(LOCAL_PATH)/third-part/hev-task-system/include
|
||||
LOCAL_CFLAGS += -DFD_SET_DEFINED -DSOCKLEN_T_DEFINED
|
||||
LOCAL_CFLAGS += $(VERSION_CFLAGS)
|
||||
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
|
||||
LOCAL_CFLAGS += -mfpu=neon
|
||||
endif
|
||||
LOCAL_STATIC_LIBRARIES := yaml lwip hev-task-system
|
||||
LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384
|
||||
LOCAL_LDFLAGS += -Wl,-z,common-page-size=16384
|
||||
include $(BUILD_EXECUTABLE)
|
||||
EXECMK
|
||||
|
||||
"$NDK_HOME/ndk-build" \
|
||||
NDK_PROJECT_PATH=. \
|
||||
APP_BUILD_SCRIPT=jni/exec.mk \
|
||||
"APP_ABI=$ABIS" \
|
||||
APP_PLATFORM=android-24 \
|
||||
NDK_LIBS_OUT="$TMPDIR/libs-exec" \
|
||||
NDK_OUT="$TMPDIR/obj-exec" \
|
||||
"APP_CFLAGS=-O3" \
|
||||
"APP_LDFLAGS=-Wl,--build-id=none -Wl,--hash-style=gnu" \
|
||||
|
||||
# Stage both artifacts under libs/<abi>/. The executable is renamed to
|
||||
# lib*.so so the APK installer extracts it into nativeLibraryDir as an
|
||||
# executable file (filename distinct from the JNI library above).
|
||||
mkdir -p "$__dir/libs"
|
||||
cp -r "$TMPDIR/libs/"* "$__dir/libs/"
|
||||
for abi in $ABIS; do
|
||||
cp "$TMPDIR/libs-exec/$abi/hevsockstun" "$__dir/libs/$abi/libhevsockstun.so"
|
||||
done
|
||||
|
||||
popd
|
||||
rm -rf $TMPDIR
|
||||
@@ -0,0 +1,84 @@
|
||||
# v2rayNG — Agent guide
|
||||
|
||||
## Build
|
||||
|
||||
```sh
|
||||
cd V2rayNG
|
||||
./gradlew assembleFdroidDebug # or assemblePlaystoreDebug
|
||||
```
|
||||
|
||||
Kotlin 2.4.0, AGP 9.2.1, Gradle Kotlin DSL. No lint/format/typecheck tasks configured.
|
||||
|
||||
## Test
|
||||
|
||||
```sh
|
||||
cd V2rayNG && ./gradlew test
|
||||
```
|
||||
|
||||
JUnit 4 + Mockito. Unit tests only under `app/src/test/java/`. No instrumented tests beyond defaults.
|
||||
|
||||
## Project structure
|
||||
|
||||
```
|
||||
V2rayNG/
|
||||
app/
|
||||
src/main/java/com/v2ray/ang/
|
||||
AngApplication.kt # extends MultiDexApplication, inits MMKV + WorkManager
|
||||
AppConfig.kt # all constants, pref keys, URLs, ports, tags
|
||||
core/ # v2ray core integration
|
||||
CoreServiceManager.kt # start/stop core, traffic stats, delay measurement
|
||||
CoreConfigManager.kt # generates JSON config for v2ray core
|
||||
CoreNativeManager.kt # JNI bridge to libv2ray AAR
|
||||
CoreOutboundBuilder.kt # outbound config construction
|
||||
CoreConfigContextBuilder.kt
|
||||
service/ # Android foreground services
|
||||
CoreVpnService.kt # VPN mode (VpnService)
|
||||
CoreProxyOnlyService.kt # proxy-only mode (no VPN)
|
||||
CoreTestService.kt # delay test
|
||||
TProxyService.kt
|
||||
DialerNativeService.kt / DialerWebviewService.kt # browser dialer
|
||||
RealPingWorkerService.kt # WorkManager-based real ping
|
||||
QSTileService.kt # quick settings tile
|
||||
ProcessService.kt
|
||||
handler/ # business logic
|
||||
MmkvManager.kt # all MMKV CRUD (servers, subs, settings, routing)
|
||||
SettingsManager.kt # preference defaults, config generation (633 lines)
|
||||
AngConfigManager.kt # server config operations
|
||||
NotificationManager.kt
|
||||
SpeedtestManager.kt
|
||||
SubscriptionUpdater.kt
|
||||
WebDavManager.kt
|
||||
UpdateCheckerManager.kt
|
||||
CertificateFingerprintManager.kt
|
||||
SettingsChangeManager.kt
|
||||
ui/ # activities, adapters, fragments
|
||||
MainActivity.kt # main screen with drawer + tabs
|
||||
ServerActivity.kt # edit server config
|
||||
ServerCustomConfigActivity.kt / ServerGroupActivity.kt / ServerProxyChainActivity.kt
|
||||
SettingsActivity.kt / PerAppProxyActivity.kt / AppPickerActivity.kt
|
||||
ScannerActivity.kt / LogcatActivity.kt
|
||||
RoutingSettingActivity.kt / RoutingEditActivity.kt
|
||||
SubSettingActivity.kt / SubEditActivity.kt
|
||||
UserAssetActivity.kt / UserAssetUrlActivity.kt
|
||||
TaskerActivity.kt / UrlSchemeActivity.kt
|
||||
BackupActivity.kt / CheckUpdateActivity.kt / AboutActivity.kt
|
||||
fmt/ # protocol URL parsers (VMESS, VLESS, TROJAN, SS, SOCKS, etc.)
|
||||
dto/ # data classes + entities/
|
||||
enums/ # EConfigType, Language, RoutingType, etc.
|
||||
extension/_Ext.kt # extension functions (toast, traffic string, etc.)
|
||||
util/ # Utils, JsonUtil, HttpUtil, LogUtil, etc.
|
||||
receiver/ # BootReceiver, TaskerReceiver, WidgetProvider
|
||||
contracts/ # interfaces (ServiceControl, Tun2SocksControl)
|
||||
helper/ # QRCodeScannerHelper, PermissionHelper, FileChooserHelper etc.
|
||||
```
|
||||
|
||||
## Key facts
|
||||
|
||||
- **Storage**: MMKV exclusively — never SharedPreferences. `MmkvManager` is the data layer.
|
||||
- **Core**: Native AAR (`libv2ray`) from [AndroidLibV2rayLite](https://github.com/2dust/AndroidLibV2rayLite) or [AndroidLibXrayLite](https://github.com/2dust/AndroidLibXrayLite). Prebuilt `.aar` files go in `app/libs/`.
|
||||
- **Services** run in dedicated process `:RunSoLibV2RayDaemon`. `CoreServiceManager` controls start/stop lifecycle.
|
||||
- **Two modes**: VPN (`CoreVpnService`, uses `VpnService.Builder`) or proxy-only (`CoreProxyOnlyService`, local SOCKS/HTTP).
|
||||
- **Flavors**: `fdroid` (suffix `.fdroid`) and `playstore` (no suffix). ABI version codes differ per flavor.
|
||||
- **hev-socks5-tunnel**: Optional tun2socks binary. Build with `./compile-hevtun.sh` (requires `NDK_HOME`).
|
||||
- **ViewBinding** enabled, no DataBinding.
|
||||
- **No CI**, no pre-commit hooks, no lint/format enforcement.
|
||||
+1
-1
Submodule hev-socks5-tunnel updated: dfabaa9cad...67dfba56c0.
Reference in new issue
Block a user