Update On Mon Feb 9 20:27:08 CET 2026

This commit is contained in:
github-action[bot]
2026-02-09 20:27:08 +01:00
parent c171cb264d
commit 2c8c480912
117 changed files with 5856 additions and 967 deletions
+1
View File
@@ -1266,3 +1266,4 @@ Update On Thu Feb 5 20:02:24 CET 2026
Update On Fri Feb 6 20:02:22 CET 2026
Update On Sat Feb 7 19:47:34 CET 2026
Update On Sun Feb 8 19:51:33 CET 2026
Update On Mon Feb 9 20:27:00 CET 2026
@@ -1,11 +1,13 @@
package com.github.kr328.clash
import android.app.ActivityManager
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.getSystemService
import com.github.kr328.clash.common.compat.isAllowForceDarkCompat
import com.github.kr328.clash.common.compat.isLightNavigationBarCompat
import com.github.kr328.clash.common.compat.isLightStatusBarsCompat
@@ -88,6 +90,11 @@ abstract class BaseActivity<D : Design<*>> : AppCompatActivity(),
super.onCreate(savedInstanceState)
applyDayNight()
// Apply excludeFromRecents setting to all app tasks.
checkNotNull(getSystemService<ActivityManager>()).appTasks.forEach { task ->
task.setExcludeFromRecents(uiStore.hideFromRecents)
}
launch {
main()
}
@@ -77,6 +77,17 @@ class AppSettingsDesign(
}
}
switch(
value = uiStore::hideFromRecents,
icon = R.drawable.ic_baseline_stack,
title = R.string.hide_from_recents_title,
summary = R.string.hide_from_recents_desc,
) {
listener = OnChangedListener {
requests.trySend(Request.ReCreateAllActivities)
}
}
category(R.string.service)
switch(
@@ -30,6 +30,11 @@ class UiStore(context: Context) {
defaultValue = false
)
var hideFromRecents: Boolean by store.boolean(
key = "hide_from_recents",
defaultValue = false,
)
var proxyExcludeNotSelectable by store.boolean(
key = "proxy_exclude_not_selectable",
defaultValue = false,
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="@android:color/white"
android:pathData="M1024,320 L512,64 0,320l512,256L1024,320zM512,149 L854,320 512,491 170,320 512,149zM921.4,460.7 L1024,512 512,768 0,512 102.6,460.7 512,665.4ZM921.4,652.7 L1024,704 512,960 0,704 102.6,652.7 512,857.4Z" />
</vector>
@@ -39,6 +39,8 @@
<string name="global_mode">全局模式</string>
<string name="hide_app_icon_title">隐藏应用图标</string>
<string name="hide_app_icon_desc">可以在拨号盘输入 *#*#252746382#*#* 打开应用</string>
<string name="hide_from_recents_title">从最近任务隐藏</string>
<string name="hide_from_recents_desc">在最近任务中隐藏应用</string>
<string name="history">历史</string>
<string name="import_from_file">从文件导入</string>
<string name="import_from_url">从 URL 导入</string>
@@ -346,6 +346,8 @@
<string name="hide_app_icon_title">Hide App Icon</string>
<string name="hide_app_icon_desc">You can dial *#*#252746382#*#* to open this App</string>
<string name="hide_from_recents_title">Hide from Recents</string>
<string name="hide_from_recents_desc">Hide app from the Recent apps screen</string>
<string name="import_from_qr_no_permission">Camera access is restricted. Please enable it in Settings.</string>
<string name="import_from_qr_exception">An unhandled system exception occurred.</string>
</resources>
+1 -1
View File
@@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx4g -XX:+UseZGC -Dfile.encoding=UTF-8
org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+1 -1
View File
@@ -60,7 +60,7 @@ func (o *overrideSchema) Apply(mapping map[string]any) error {
mapping["skip-cert-verify"] = *o.SkipCertVerify
}
if o.Interface != nil {
mapping["interface"] = *o.Interface
mapping["interface-name"] = *o.Interface
}
if o.RoutingMark != nil {
mapping["routing-mark"] = *o.RoutingMark
+3 -3
View File
@@ -55,7 +55,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: actions/cache@v5
name: Cache Rust dependencies
@@ -142,7 +142,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: actions/cache@v5
name: Cache Rust dependencies
@@ -233,7 +233,7 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: actions/cache@v5
name: Cache Rust dependencies
+2 -2
View File
@@ -16,7 +16,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: '24'
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -57,7 +57,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: '24'
- uses: pnpm/action-setup@v4
name: Install pnpm
+1 -1
View File
@@ -82,7 +82,7 @@ jobs:
- name: Install Node latest
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: pnpm/action-setup@v4
name: Install pnpm
+1 -1
View File
@@ -68,7 +68,7 @@ jobs:
- name: Install Node latest
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
@@ -88,7 +88,7 @@ jobs:
- name: Install Node latest
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: pnpm/action-setup@v4
name: Install pnpm
+1 -1
View File
@@ -45,7 +45,7 @@ jobs:
- name: Install Node latest
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: pnpm/action-setup@v4
name: Install pnpm
@@ -27,7 +27,7 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: pnpm/action-setup@v4
name: Install pnpm
+2
View File
@@ -19,6 +19,8 @@ jobs:
update_tag:
name: Update tag
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
- name: Install Node
uses: actions/setup-node@v6
with:
node-version: '22'
node-version: '24'
- uses: pnpm/action-setup@v4
name: Install pnpm
+2 -2
View File
@@ -31,7 +31,7 @@ jobs:
- name: Prepare Node
uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
- uses: pnpm/action-setup@v4
name: Install pnpm
with:
@@ -81,7 +81,7 @@ jobs:
GITHUB_REPO: ${{ github.repository }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v6
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: 'chore: bump version to v${{ steps.update-version.outputs.version }}'
commit_user_name: 'github-actions[bot]'
@@ -76,9 +76,9 @@
"@iconify/json": "2.2.437",
"@monaco-editor/react": "4.7.0",
"@tanstack/react-query": "5.90.20",
"@tanstack/react-router": "1.158.4",
"@tanstack/react-router-devtools": "1.158.4",
"@tanstack/router-plugin": "1.158.4",
"@tanstack/react-router": "1.159.4",
"@tanstack/react-router-devtools": "1.159.4",
"@tanstack/router-plugin": "1.159.4",
"@tauri-apps/plugin-clipboard-manager": "2.3.2",
"@tauri-apps/plugin-dialog": "2.6.0",
"@tauri-apps/plugin-fs": "2.4.5",
@@ -102,14 +102,14 @@
"nanoid": "5.1.6",
"sass-embedded": "1.97.3",
"shiki": "2.5.0",
"unplugin-auto-import": "20.3.0",
"unplugin-icons": "22.5.0",
"unplugin-auto-import": "21.0.0",
"unplugin-icons": "23.0.1",
"validator": "13.15.26",
"vite": "7.3.1",
"vite-plugin-html": "3.2.2",
"vite-plugin-sass-dts": "1.3.35",
"vite-plugin-svgr": "4.5.0",
"vite-tsconfig-paths": "5.1.4",
"vite-tsconfig-paths": "6.1.0",
"zod": "4.3.6"
}
}
+1 -1
View File
@@ -31,7 +31,7 @@
"react-use": "17.6.0",
"tailwindcss": "4.1.18",
"vite": "7.3.1",
"vite-tsconfig-paths": "5.1.4"
"vite-tsconfig-paths": "6.1.0"
},
"devDependencies": {
"@emotion/react": "11.14.0",
+2 -2
View File
@@ -1,7 +1,7 @@
{
"manifest_version": 1,
"latest": {
"mihomo": "v1.19.19",
"mihomo": "v1.19.20",
"mihomo_alpha": "alpha-97f2525",
"clash_rs": "v0.9.4",
"clash_premium": "2023-09-05-gdcc8d87",
@@ -69,5 +69,5 @@
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
}
},
"updated_at": "2026-02-07T22:32:42.838Z"
"updated_at": "2026-02-08T22:48:51.230Z"
}
+2 -2
View File
@@ -93,9 +93,9 @@
"tsx": "4.21.0",
"typescript": "5.9.3"
},
"packageManager": "pnpm@10.29.1",
"packageManager": "pnpm@10.29.2",
"engines": {
"node": "22.22.0"
"node": "24.13.0"
},
"pnpm": {
"overrides": {
+83 -124
View File
@@ -241,7 +241,7 @@ importers:
version: 3.13.18(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-zod-adapter':
specifier: 1.81.5
version: 1.81.5(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)
version: 1.81.5(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)
'@tauri-apps/api':
specifier: 2.10.1
version: 2.10.1
@@ -355,14 +355,14 @@ importers:
specifier: 5.90.20
version: 5.90.20(react@19.2.4)
'@tanstack/react-router':
specifier: 1.158.4
version: 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
specifier: 1.159.4
version: 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-router-devtools':
specifier: 1.158.4
version: 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
specifier: 1.159.4
version: 1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-plugin':
specifier: 1.158.4
version: 1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
specifier: 1.159.4
version: 1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
'@tauri-apps/plugin-clipboard-manager':
specifier: 2.3.2
version: 2.3.2
@@ -433,11 +433,11 @@ importers:
specifier: 2.5.0
version: 2.5.0
unplugin-auto-import:
specifier: 20.3.0
version: 20.3.0
specifier: 21.0.0
version: 21.0.0
unplugin-icons:
specifier: 22.5.0
version: 22.5.0(@svgr/core@8.1.0(typescript@5.9.3))
specifier: 23.0.1
version: 23.0.1(@svgr/core@8.1.0(typescript@5.9.3))
validator:
specifier: 13.15.26
version: 13.15.26
@@ -454,8 +454,8 @@ importers:
specifier: 4.5.0
version: 4.5.0(rollup@4.46.2)(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
vite-tsconfig-paths:
specifier: 5.1.4
version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
specifier: 6.1.0
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
zod:
specifier: 4.3.6
version: 4.3.6
@@ -523,8 +523,8 @@ importers:
specifier: 7.3.1
version: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
vite-tsconfig-paths:
specifier: 5.1.4
version: 5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
specifier: 6.1.0
version: 6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))
devDependencies:
'@emotion/react':
specifier: 11.14.0
@@ -640,9 +640,6 @@ packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@antfu/utils@9.2.0':
resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==}
'@babel/code-frame@7.27.1':
resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==}
engines: {node: '>=6.9.0'}
@@ -1678,8 +1675,8 @@ packages:
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@3.0.2':
resolution: {integrity: sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==}
'@iconify/utils@3.1.0':
resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==}
'@inlang/paraglide-js@2.7.1':
resolution: {integrity: sha512-wCpnS9iRTRYMilvWBjB0ndf8+moon+AXz23Uh4wbpQjWhRJyvCytkGFzm7jeqAGggK4v3oeuyjva91TDMS+qhw==}
@@ -3372,20 +3369,20 @@ packages:
peerDependencies:
react: ^18 || ^19
'@tanstack/react-router-devtools@1.158.4':
resolution: {integrity: sha512-/EkrrJGTPC7MwLfcYYmZM71ANDMLbwcYvBtDA+48LqHUKal8mpWlaodiWdFFnVQ7ny/unbUxljgdrNV9YZiyFQ==}
'@tanstack/react-router-devtools@1.159.4':
resolution: {integrity: sha512-7HXV4b5WZMdWoP6HD+mURh4mq1ssRg0dfcVYx+AzhaLboFzy4LyzdUtMpmNgRFgz3mBXLBoo+gMbKSjKlmsZmw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': ^1.158.4
'@tanstack/router-core': ^1.158.4
'@tanstack/react-router': ^1.159.4
'@tanstack/router-core': ^1.159.4
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
peerDependenciesMeta:
'@tanstack/router-core':
optional: true
'@tanstack/react-router@1.158.4':
resolution: {integrity: sha512-i15xXumgvpuM+4NSuIwgouGezuj9eHjZsgpTZSQ7E9pa8rYmhZbWnf8xU68qaLmaKIol/e75o/YzVH2QWHs3iQ==}
'@tanstack/react-router@1.159.4':
resolution: {integrity: sha512-z3DhNkRh/joky5b+X4jEYOn9q4Jieie6mVFP62wgwM9pVlNRYh6aIroiU95ZyOwDXDijItVEZtvHuipbLHy4jw==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -3416,30 +3413,30 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/router-core@1.158.4':
resolution: {integrity: sha512-KikgYdyrEFqsjjgv9pMhDTMmASMAyFRvUiKFdQPQtXq3aD1qv/zck4CbA4bfzp9N9nYu/qvWwU1mlYU4u5JeXg==}
'@tanstack/router-core@1.159.4':
resolution: {integrity: sha512-MFzPH39ijNO83qJN3pe7x4iAlhZyqgao3sJIzv3SJ4Pnk12xMnzuDzIAQT/1WV6JolPQEcw0Wr4L5agF8yxoeg==}
engines: {node: '>=12'}
'@tanstack/router-devtools-core@1.158.4':
resolution: {integrity: sha512-9MKzstYp/6sNRSwJY2b9ipVW8b8/x1iSFNfLhOJur2tnjB3RhwCDfy0u+to70BrRpBEWeq7jvJoVdP029gzUUg==}
'@tanstack/router-devtools-core@1.159.4':
resolution: {integrity: sha512-qMUeIv+6n1mZOcO2raCIbdOeDeMpJEmgm6oMs/nWEG61lYrzJYaCcpBTviAX0nRhSiQSUCX9cHiosUEA0e2HAw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/router-core': ^1.158.4
'@tanstack/router-core': ^1.159.4
csstype: ^3.0.10
peerDependenciesMeta:
csstype:
optional: true
'@tanstack/router-generator@1.158.4':
resolution: {integrity: sha512-RQmqMTT0oV8dS/3Glcq9SPzDZqOPyKb/LVFUkNoTfMwW88WyGnQcYqZAkmVk/CGBWWDfwObOUZoGq5jTF7bG8w==}
'@tanstack/router-generator@1.159.4':
resolution: {integrity: sha512-O8tICQoSuvK6vs3mvBdI3zVLFmYfj/AYDCX0a5msSADP/2S0GsgDDTB5ah731TqYCtjeNriaWz9iqst38cjF/Q==}
engines: {node: '>=12'}
'@tanstack/router-plugin@1.158.4':
resolution: {integrity: sha512-g2sytAhljw6Jd6Klu37OZ75+o+vhiGdbWtnBy/4rYLC4NN6hSnjgJQRI3+h1CI1KQ4EUgsZYZr/hgE1KHoiWYQ==}
'@tanstack/router-plugin@1.159.4':
resolution: {integrity: sha512-xXLUPwIf1Y+VGrpryHZYoJoG7V5evxTkmP64CYm6JEJGTb3hai/syhZb69iVQYb4f4IR5LqEL7VgagnlekdAWw==}
engines: {node: '>=12'}
peerDependencies:
'@rsbuild/core': '>=1.0.2'
'@tanstack/react-router': ^1.158.4
'@tanstack/react-router': ^1.159.4
vite: '>=5.0.0 || >=6.0.0 || >=7.0.0'
vite-plugin-solid: ^2.11.10
webpack: '>=5.92.0'
@@ -4681,15 +4678,6 @@ packages:
supports-color:
optional: true
debug@4.3.7:
resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@4.4.0:
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
engines: {node: '>=6.0'}
@@ -5087,10 +5075,6 @@ packages:
resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
engines: {node: '>=4'}
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
globals@17.3.0:
resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
engines: {node: '>=18'}
@@ -5959,6 +5943,9 @@ packages:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
octokit@5.0.5:
resolution: {integrity: sha512-4+/OFSqOjoyULo7eN7EA97DE0Xydj/PW5aIckxqQIoFjFwqXKuFCvXUJObyJfBF9Khu4RL/jlDRI9FPaMGfPnw==}
engines: {node: '>= 20'}
@@ -6827,6 +6814,11 @@ packages:
peerDependencies:
kysely: '*'
srvx@0.11.2:
resolution: {integrity: sha512-u6NbjE84IJwm1XUnJ53WqylLTQ3BdWRw03lcjBNNeMBD+EFjkl0Cnw1RVaGSqRAo38pOHOPXJH30M6cuTINUxw==}
engines: {node: '>=20.16.0'}
hasBin: true
stack-generator@2.0.10:
resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==}
@@ -7152,8 +7144,8 @@ packages:
unified@11.0.4:
resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
unimport@5.5.0:
resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==}
unimport@5.6.0:
resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==}
engines: {node: '>=18.12.0'}
unist-util-is@6.0.0:
@@ -7187,9 +7179,9 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
unplugin-auto-import@20.3.0:
resolution: {integrity: sha512-RcSEQiVv7g0mLMMXibYVKk8mpteKxvyffGuDKqZZiFr7Oq3PB1HwgHdK5O7H4AzbhzHoVKG0NnMnsk/1HIVYzQ==}
engines: {node: '>=14'}
unplugin-auto-import@21.0.0:
resolution: {integrity: sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==}
engines: {node: '>=20.19.0'}
peerDependencies:
'@nuxt/kit': ^4.0.0
'@vueuse/core': '*'
@@ -7199,15 +7191,13 @@ packages:
'@vueuse/core':
optional: true
unplugin-icons@22.5.0:
resolution: {integrity: sha512-MBlMtT5RuMYZy4TZgqUL2OTtOdTUVsS1Mhj6G1pEzMlFJlEnq6mhUfoIt45gBWxHcsOdXJDWLg3pRZ+YmvAVWQ==}
unplugin-icons@23.0.1:
resolution: {integrity: sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==}
peerDependencies:
'@svgr/core': '>=7.0.0'
'@svgx/core': ^1.0.1
'@vue/compiler-sfc': ^3.0.2 || ^2.7.0
'@vue/compiler-sfc': ^3.0.2
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
vue-template-compiler: ^2.6.12
vue-template-es2015-compiler: ^1.9.0
peerDependenciesMeta:
'@svgr/core':
optional: true
@@ -7217,19 +7207,11 @@ packages:
optional: true
svelte:
optional: true
vue-template-compiler:
optional: true
vue-template-es2015-compiler:
optional: true
unplugin-utils@0.3.1:
resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==}
engines: {node: '>=20.19.0'}
unplugin@2.3.10:
resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==}
engines: {node: '>=18.12.0'}
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
@@ -7360,13 +7342,10 @@ packages:
peerDependencies:
vite: '>=2.6.0'
vite-tsconfig-paths@5.1.4:
resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==}
vite-tsconfig-paths@6.1.0:
resolution: {integrity: sha512-kpd3sY9glHIDaq4V/Tlc1Y8WaKtutoc3B525GHxEVKWX42FKfQsXvjFOemu1I8VIN8pNbrMLWVTbW79JaRUxKg==}
peerDependencies:
vite: '*'
peerDependenciesMeta:
vite:
optional: true
vite@7.3.1:
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
@@ -7564,8 +7543,6 @@ snapshots:
package-manager-detector: 1.3.0
tinyexec: 1.0.1
'@antfu/utils@9.2.0': {}
'@babel/code-frame@7.27.1':
dependencies:
'@babel/helper-validator-identifier': 7.28.5
@@ -8812,18 +8789,11 @@ snapshots:
'@iconify/types@2.0.0': {}
'@iconify/utils@3.0.2':
'@iconify/utils@3.1.0':
dependencies:
'@antfu/install-pkg': 1.1.0
'@antfu/utils': 9.2.0
'@iconify/types': 2.0.0
debug: 4.4.3
globals: 15.15.0
kolorist: 1.8.0
local-pkg: 1.1.2
mlly: 1.8.0
transitivePeerDependencies:
- supports-color
'@inlang/paraglide-js@2.7.1(babel-plugin-macros@3.1.0)':
dependencies:
@@ -10384,25 +10354,26 @@ snapshots:
'@tanstack/query-core': 5.90.20
react: 19.2.4
'@tanstack/react-router-devtools@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.158.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
'@tanstack/react-router-devtools@1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@tanstack/router-core@1.159.4)(csstype@3.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-devtools-core': 1.158.4(@tanstack/router-core@1.158.4)(csstype@3.2.3)
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-devtools-core': 1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3)
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
optionalDependencies:
'@tanstack/router-core': 1.158.4
'@tanstack/router-core': 1.159.4
transitivePeerDependencies:
- csstype
'@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
'@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@tanstack/history': 1.154.14
'@tanstack/react-store': 0.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/router-core': 1.158.4
'@tanstack/router-core': 1.159.4
isbot: 5.1.28
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
srvx: 0.11.2
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
@@ -10431,7 +10402,7 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@tanstack/router-core@1.158.4':
'@tanstack/router-core@1.159.4':
dependencies:
'@tanstack/history': 1.154.14
'@tanstack/store': 0.8.0
@@ -10441,18 +10412,18 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/router-devtools-core@1.158.4(@tanstack/router-core@1.158.4)(csstype@3.2.3)':
'@tanstack/router-devtools-core@1.159.4(@tanstack/router-core@1.159.4)(csstype@3.2.3)':
dependencies:
'@tanstack/router-core': 1.158.4
'@tanstack/router-core': 1.159.4
clsx: 2.1.1
goober: 2.1.16(csstype@3.2.3)
tiny-invariant: 1.3.3
optionalDependencies:
csstype: 3.2.3
'@tanstack/router-generator@1.158.4':
'@tanstack/router-generator@1.159.4':
dependencies:
'@tanstack/router-core': 1.158.4
'@tanstack/router-core': 1.159.4
'@tanstack/router-utils': 1.158.0
'@tanstack/virtual-file-routes': 1.154.7
prettier: 3.8.1
@@ -10463,7 +10434,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-plugin@1.158.4(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))':
'@tanstack/router-plugin@1.159.4(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0)
@@ -10471,15 +10442,15 @@ snapshots:
'@babel/template': 7.28.6
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
'@tanstack/router-core': 1.158.4
'@tanstack/router-generator': 1.158.4
'@tanstack/router-core': 1.159.4
'@tanstack/router-generator': 1.159.4
'@tanstack/router-utils': 1.158.0
'@tanstack/virtual-file-routes': 1.154.7
chokidar: 3.6.0
unplugin: 2.3.11
zod: 3.25.76
optionalDependencies:
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
vite: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -10498,9 +10469,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)':
'@tanstack/router-zod-adapter@1.81.5(@tanstack/react-router@1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(zod@4.3.6)':
dependencies:
'@tanstack/react-router': 1.158.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
'@tanstack/react-router': 1.159.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
zod: 4.3.6
'@tanstack/store@0.8.0': {}
@@ -11806,10 +11777,6 @@ snapshots:
dependencies:
ms: 2.0.0
debug@4.3.7:
dependencies:
ms: 2.1.3
debug@4.4.0:
dependencies:
ms: 2.1.3
@@ -12195,8 +12162,6 @@ snapshots:
globals@11.12.0:
optional: true
globals@15.15.0: {}
globals@17.3.0: {}
globby@16.1.0:
@@ -13135,6 +13100,8 @@ snapshots:
object-assign@4.1.1: {}
obug@2.1.1: {}
octokit@5.0.5:
dependencies:
'@octokit/app': 16.1.2
@@ -13969,6 +13936,8 @@ snapshots:
'@sqlite.org/sqlite-wasm': 3.48.0-build4
kysely: 0.27.6
srvx@0.11.2: {}
stack-generator@2.0.10:
dependencies:
stackframe: 1.3.4
@@ -14346,7 +14315,7 @@ snapshots:
trough: 2.2.0
vfile: 6.0.1
unimport@5.5.0:
unimport@5.6.0:
dependencies:
acorn: 8.15.0
escape-string-regexp: 5.0.0
@@ -14399,39 +14368,30 @@ snapshots:
universalify@2.0.1: {}
unplugin-auto-import@20.3.0:
unplugin-auto-import@21.0.0:
dependencies:
local-pkg: 1.1.2
magic-string: 0.30.21
picomatch: 4.0.3
unimport: 5.5.0
unimport: 5.6.0
unplugin: 2.3.11
unplugin-utils: 0.3.1
unplugin-icons@22.5.0(@svgr/core@8.1.0(typescript@5.9.3)):
unplugin-icons@23.0.1(@svgr/core@8.1.0(typescript@5.9.3)):
dependencies:
'@antfu/install-pkg': 1.1.0
'@iconify/utils': 3.0.2
debug: 4.4.3
'@iconify/utils': 3.1.0
local-pkg: 1.1.2
unplugin: 2.3.10
obug: 2.1.1
unplugin: 2.3.11
optionalDependencies:
'@svgr/core': 8.1.0(typescript@5.9.3)
transitivePeerDependencies:
- supports-color
unplugin-utils@0.3.1:
dependencies:
pathe: 2.0.3
picomatch: 4.0.3
unplugin@2.3.10:
dependencies:
'@jridgewell/remapping': 2.3.5
acorn: 8.15.0
picomatch: 4.0.3
webpack-virtual-modules: 0.6.2
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -14574,12 +14534,11 @@ snapshots:
- supports-color
- typescript
vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)):
vite-tsconfig-paths@6.1.0(typescript@5.9.3)(vite@7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)):
dependencies:
debug: 4.3.7
debug: 4.4.3
globrex: 0.1.2
tsconfck: 3.0.3(typescript@5.9.3)
optionalDependencies:
vite: 7.3.1(@types/node@24.10.12)(jiti@2.6.1)(less@4.2.0)(lightningcss@1.30.2)(sass-embedded@1.97.3)(sass@1.97.3)(stylus@0.62.0)(terser@5.36.0)(tsx@4.21.0)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
+7
View File
@@ -19,6 +19,7 @@ import (
"crypto/sha256"
"fmt"
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"github.com/enfein/mieru/v3/pkg/metrics"
)
@@ -100,6 +101,12 @@ type BlockCipher interface {
// SetBlockContext sets the BlockContext.
SetBlockContext(bc BlockContext)
// NoncePattern returns a copy of NoncePattern associated with the cipher block.
NoncePattern() *appctlpb.NoncePattern
// SetNoncePattern sets the NoncePattern associated with the cipher block.
SetNoncePattern(pattern *appctlpb.NoncePattern)
}
// BlockContext contains optional context associated to a cipher block.
+16
View File
@@ -22,8 +22,10 @@ import (
"fmt"
"sync"
"github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
"github.com/enfein/mieru/v3/pkg/common"
"golang.org/x/crypto/chacha20poly1305"
"google.golang.org/protobuf/proto"
)
const (
@@ -52,6 +54,7 @@ type AEADBlockCipher struct {
implicitNonce []byte
mu sync.Mutex
ctx BlockContext
noncePattern *appctlpb.NoncePattern
}
// newAESGCMBlockCipher creates a new AES-GCM cipher with the supplied key.
@@ -261,6 +264,7 @@ func (c *AEADBlockCipher) Clone() BlockCipher {
copy(newCipher.implicitNonce, c.implicitNonce)
}
newCipher.ctx = c.ctx
newCipher.noncePattern = proto.Clone(c.noncePattern).(*appctlpb.NoncePattern)
return newCipher
}
@@ -291,6 +295,18 @@ func (c *AEADBlockCipher) SetBlockContext(bc BlockContext) {
c.ctx = bc
}
func (c *AEADBlockCipher) NoncePattern() *appctlpb.NoncePattern {
c.mu.Lock()
defer c.mu.Unlock()
return proto.Clone(c.noncePattern).(*appctlpb.NoncePattern)
}
func (c *AEADBlockCipher) SetNoncePattern(pattern *appctlpb.NoncePattern) {
c.mu.Lock()
defer c.mu.Unlock()
c.noncePattern = proto.Clone(pattern).(*appctlpb.NoncePattern)
}
// newNonce generates a new nonce.
func (c *AEADBlockCipher) newNonce() ([]byte, error) {
nonce := make([]byte, c.NonceSize())
+11
View File
@@ -35,6 +35,7 @@ import (
"github.com/enfein/mieru/v3/pkg/mathext"
"github.com/enfein/mieru/v3/pkg/sockopts"
"github.com/enfein/mieru/v3/pkg/stderror"
"github.com/enfein/mieru/v3/pkg/trafficpattern"
)
const (
@@ -53,6 +54,7 @@ type Mux struct {
clientDNSConfig *apicommon.ClientDNSConfig
streamListenerFactory apicommon.StreamListenerFactory
packetListenerFactory apicommon.PacketListenerFactory
trafficPattern *trafficpattern.Config
chAccept chan net.Conn
acceptHasErr atomic.Bool
acceptErr chan error // this channel is closed when accept has error
@@ -202,6 +204,15 @@ func (m *Mux) SetPacketListenerFactory(listenerFactory apicommon.PacketListenerF
return m
}
// SetTrafficPattern updates the traffic pattern configuration used by the mux.
func (m *Mux) SetTrafficPattern(trafficPattern *trafficpattern.Config) *Mux {
m.mu.Lock()
defer m.mu.Unlock()
m.trafficPattern = trafficPattern
log.Infof("Mux traffic pattern has been updated")
return m
}
// SetClientUserNamePassword panics if the mux is already started.
func (m *Mux) SetClientUserNamePassword(username string, password []byte) *Mux {
m.mu.Lock()
+1 -1
View File
@@ -60,7 +60,7 @@ func (o *overrideSchema) Apply(mapping map[string]any) error {
mapping["skip-cert-verify"] = *o.SkipCertVerify
}
if o.Interface != nil {
mapping["interface"] = *o.Interface
mapping["interface-name"] = *o.Interface
}
if o.RoutingMark != nil {
mapping["routing-mark"] = *o.RoutingMark
+2 -2
View File
@@ -1,4 +1,4 @@
PACKAGE_NAME=moe.nb4a
VERSION_NAME=1.4.1
VERSION_NAME=1.4.2
PRE_VERSION_NAME=pre-1.4.2-20260202-1
VERSION_CODE=45
VERSION_CODE=46
@@ -38,6 +38,10 @@ local ss_method_list = {
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
local xray_version = api.get_app_version("xray")
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
@@ -545,17 +549,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
o:value("none", "none")
o:value("header-srtp", "srtp")
o:value("header-utp", "utp")
o:value("header-wechat", "wechat-video")
o:value("header-dtls", "dtls")
o:value("header-wireguard", "wireguard")
o:value("header-dns", "dns")
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "header-dns" })
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
@@ -720,7 +718,7 @@ o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
o.default = 8
o:depends({ [_n("mux")] = true })
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
--[[tcpMptcp]]
@@ -45,9 +45,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
o.default = 0
@@ -46,9 +46,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
o.default = 0
@@ -68,8 +68,7 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
api.luci_types(arg[1], m, s, type_name, option_prefix)
@@ -32,9 +32,8 @@ o.datatype = "port"
o = s:option(Value, _n("password"), translate("Password"))
o.password = true
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, _n("tls"), translate("TLS"))
o.default = 0
@@ -20,6 +20,10 @@ local x_ss_method_list = {
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
-- [[ Xray ]]
s.fields["type"]:value(type_name, "Xray")
@@ -314,17 +318,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP部分 ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
o:value("none", "none")
o:value("header-srtp", "srtp")
o:value("header-utp", "utp")
o:value("header-wechat", "wechat-video")
o:value("header-dtls", "dtls")
o:value("header-wireguard", "wireguard")
o:value("header-dns", "dns")
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "header-dns" })
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
@@ -364,6 +362,10 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"),
o.default = "0"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ [_n("custom")] = false })
-- [[ Fallback部分 ]]--
o = s:option(Flag, _n("fallback"), translate("Fallback"))
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
@@ -75,7 +75,7 @@ function gen_config(var)
password = node.password,
method = node.method,
timeout = tonumber(node.timeout),
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
reuse_port = true,
tcp_tproxy = var["tcp_tproxy"] and true or nil
}
@@ -103,7 +103,7 @@ function gen_config(var)
}
},
locals = {},
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
}
if local_socks_address and local_socks_port then
table.insert(config.locals, {
@@ -87,7 +87,7 @@ function gen_config(var)
no_delay = true,
keep_alive = true,
reuse_port = true,
fast_open = (node.tcp_fast_open == "true") and true or false,
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
fast_open_qlen = 20
}
}
@@ -286,9 +286,11 @@ function gen_outbound(flag, node, tag, proxy_table)
finalmask = (node.transport == "mkcp") and {
udp = (function()
local t = {}
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
if node.mkcp_guise and node.mkcp_guise ~= "none" then
local g = { type = node.mkcp_guise }
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
local g = { type = map[node.mkcp_guise] }
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
g.settings = { domain = node.mkcp_domain }
end
t[#t + 1] = g
@@ -597,9 +599,11 @@ function gen_config_server(node)
finalmask = (node.transport == "mkcp") and {
udp = (function()
local t = {}
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
if node.mkcp_guise and node.mkcp_guise ~= "none" then
local g = { type = node.mkcp_guise }
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
local g = { type = map[node.mkcp_guise] }
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
g.settings = { domain = node.mkcp_domain }
end
t[#t + 1] = g
@@ -613,6 +617,7 @@ function gen_config_server(node)
end)()
} or nil,
sockopt = {
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
}
}
@@ -243,6 +243,9 @@ local current_node = map:get(section)
}
var params = "";
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
var v_plugin_dom = opt.get(dom_prefix + "plugin");
if (v_plugin_dom) {
var v_plugin = v_plugin_dom.value;
@@ -322,6 +325,8 @@ local current_node = map:get(section)
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "uot")?.checked && (params += "&udp=1");
if (opt.get(dom_prefix + "shadowtls")?.checked) {
let st_plugin_str = "";
let st_version = opt.get(dom_prefix + "shadowtls_version")?.value;
@@ -418,6 +423,9 @@ local current_node = map:get(section)
info.tls = "tls";
info.sni = opt.get(dom_prefix + "tls_serverName").value;
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (info.tfo = "1");
url = b64EncodeUnicode(JSON.stringify(info));
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "vless") {
protocol = "vless";
@@ -502,6 +510,8 @@ local current_node = map:get(section)
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
params = params.substring(1);
@@ -567,6 +577,9 @@ local current_node = map:get(section)
params += opt.query("vcn", dom_prefix + "tls_CertByName");
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
params = params.substring(1);
@@ -944,11 +957,14 @@ local current_node = map:get(section)
pluginOpts = pluginParams.join(";");
}
if (has_xray && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "xray" || queryParam.type == "xhttp")) {
const needUpgrade = ss_type !== "Xray" && ss_type !== "sing-box" &&
queryParam.type && queryParam.type !== "tcp" &&
queryParam.headerType && queryParam.headerType !== "none";
if (has_xray && (ss_type == "xray" || needUpgrade || queryParam.type === "xhttp")) {
dom_prefix = "xray_"
opt.set('type', "Xray");
opt.set(dom_prefix + 'protocol', "shadowsocks");
} else if (has_singbox && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "sing-box")) {
} else if (has_singbox && (ss_type == "sing-box" || needUpgrade)) {
dom_prefix = "singbox_"
opt.set('type', "sing-box");
opt.set(dom_prefix + 'protocol', "shadowsocks");
@@ -977,6 +993,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'password', password || "");
opt.set(dom_prefix + 'method', method || "");
opt.set(dom_prefix + 'ss_method', method || "");
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (plugin && plugin != "none") {
plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin;
opt.set(dom_prefix + 'plugin_enabled', true);
@@ -1114,6 +1131,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
}
opt.set(dom_prefix + 'uot', queryParam.udp);
if (queryParam["shadow-tls"]) {
//解析SS Shadow-TLS 插件参数
@@ -1273,6 +1292,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tls_allowInsecure', false);
}
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
}
@@ -1367,6 +1388,8 @@ local current_node = map:get(section)
} else if (ssm.net === "grpc") {
opt.set(dom_prefix + 'grpc_serviceName', ssm.path);
}
opt.set(dom_prefix + 'tcp_fast_open', ssm.tfo);
}
if (ssu[0] === "vless") {
if (vless_type == "sing-box" && has_singbox) {
@@ -1518,6 +1541,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
}
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
}
@@ -1697,12 +1722,14 @@ local current_node = map:get(section)
}
document.addEventListener("DOMContentLoaded", function () {
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
} else {
sessionStorage.removeItem("fromUrl");
}
setTimeout(function () {
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
} else {
sessionStorage.removeItem("fromUrl");
}
}, 500);
})
//]]></script>
@@ -620,6 +620,8 @@ local function processData(szType, content, add_mode, group)
result.tls = "0"
end
result.tcp_fast_open = info.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
return nil
@@ -723,13 +725,17 @@ local function processData(szType, content, add_mode, group)
result.method = method
result.password = password
result.tcp_fast_open = params.tfo
if has_xray and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
result.type = 'Xray'
result.protocol = 'shadowsocks'
elseif has_singbox and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
result.type = 'sing-box'
result.protocol = 'shadowsocks'
local need_upgrade = (result.type ~= "Xray" and result.type ~= "sing-box")
and (params.type and params.type ~= "tcp")
and (params.headerType and params.headerType ~= "none")
if has_xray and (need_upgrade or params.type == "xhttp") then
result.type = "Xray"
result.protocol = "shadowsocks"
elseif has_singbox and need_upgrade then
result.type = "sing-box"
result.protocol = "shadowsocks"
end
if result.plugin then
@@ -892,7 +898,8 @@ local function processData(szType, content, add_mode, group)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
else
result.uot = params.udp
elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then
result.error_msg = "请更换Xray或Sing-Box来支持SS更多的传输方式."
end
end
@@ -1091,6 +1098,7 @@ local function processData(szType, content, add_mode, group)
end
result.alpn = params.alpn
result.tcp_fast_open = params.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
@@ -1281,6 +1289,8 @@ local function processData(szType, content, add_mode, group)
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.tcp_fast_open = params.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log("跳过节点:" .. result.remarks ..",因Sing-Box不支持" .. szType .. "协议的" .. result.transport .. "传输方式,需更换Xray。")
return nil
+272
View File
@@ -0,0 +1,272 @@
# PassWall2
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![OpenWrt](https://img.shields.io/badge/OpenWrt-21.02%2B-blue)](https://openwrt.org/)
[![LuCI](https://img.shields.io/badge/LuCI-19.07%2B-green)](https://github.com/openwrt/luci)
PassWall2 is a powerful LuCI web interface application for OpenWrt that provides advanced proxy and VPN functionality. It's a comprehensive solution for network traffic management, proxy services, and access control on OpenWrt-based routers.
## 🛠️ Installation
### Method 1: Using OpenWrt Package Manager
1. **Update package lists:**
```bash
opkg update
```
2. **Install PassWall2:**
```bash
opkg install luci-app-passwall2
```
3. **Restart LuCI:**
```bash
/etc/init.d/rpcd restart
```
### Method 2: Manual Installation
1. **Download the package:**
```bash
wget https://github.com/Openwrt-Passwall/openwrt-passwall2/releases/latest/download/luci-app-passwall2_*.ipk
```
2. **Install the package:**
```bash
opkg install luci-app-passwall2_*.ipk
```
<details>
<summary>📋 System Requirements </summary>
### OpenWrt Version
- OpenWrt 21.02 or later
- LuCI 19.07 or later
### Hardware Requirements
- Minimum 64MB RAM (128MB recommended)
- Sufficient storage for packages (varies by protocol selection)
- Network interface support for transparent proxy
### Dependencies
The following packages are automatically installed based on your configuration:
- `coreutils`, `curl`, `ip-full`, `libuci-lua`, `lua`, `luci-compat`
- Protocol-specific packages (selected during installation)
</details>
<details>
<summary>⚙️ Configuration </summary>
### Basic Setup
1. **Access LuCI Interface:**
- Navigate to `Services``PassWall2` in your OpenWrt web interface
2. **Add Your First Node:**
- Go to `Node List``Add Node`
- Select your protocol (e.g., Shadowsocks, V2Ray, etc.)
- Fill in server details (address, port, password, encryption)
3. **Configure Basic Settings:**
- Go to `Basic Settings`
- Select your default node
- Configure DNS settings
- Enable transparent proxy
4. **Apply Configuration:**
- Click `Save & Apply`
- Wait for services to start
</details>
<details>
<summary>🚀 Features</summary>
### Multi-Protocol Proxy Support
- **Shadowsocks** (Libev & Rust implementations)
- **V2Ray/Xray** with full protocol support
- **Trojan** and **Trojan-Go**
- **NaiveProxy** for advanced obfuscation
- **Hysteria** and **Hysteria2** for high-performance UDP transport
- **Sing-Box** with modern proxy features
- **ShadowsocksR** legacy support
- **WireGuard** integration
### Advanced Traffic Management
- **Load Balancing**: Distribute traffic across multiple nodes
- **URL Testing**: Automatically test and select optimal nodes
- **Smart Routing**: Domain-based and geo-based routing rules
- **DNS Manipulation**: Advanced DNS filtering and manipulation
- **Traffic Sniffing**: Protocol detection and classification
### Node Management
- **Subscription Support**: Import nodes from subscription URLs
- **QR Code Generation**: Generate and scan QR codes for node sharing
- **Node Testing**: Built-in latency and connectivity testing
- **Health Checks**: Automatic node health monitoring
- **Failover Support**: Automatic failover to backup nodes
### Access Control
- **Per-Device Rules**: Configure proxy settings per device
- **Domain Filtering**: Whitelist/blacklist domains
- **IP Filtering**: IP-based access control
- **Interface Control**: Route traffic based on network interfaces
- **Time-based Rules**: Schedule proxy usage by time
### Server-Side Support
- **Multi-User Server**: Host proxy services with user management
- **Protocol Support**: All client protocols available as servers
- **User Management**: Create and manage server users
- **Traffic Monitoring**: Monitor server usage and statistics
### Advanced Features
- **Multi-WAN Support**: Advanced routing for multi-WAN setups
- **IPv6 Support**: Full IPv6 transparency and proxy support
- **Transparent Proxy**: Transparent proxy for entire network
- **Socks5/HTTP Proxy**: Local proxy server support
- **NAT/Firewall Integration**: Seamless integration with OpenWrt firewall
</details>
<details>
<summary>🔧 Advanced Configuration</summary>
#### Load Balancing
1. Create multiple nodes of the same type
2. Go to `Node List``Add Node``Load Balancing`
3. Select nodes to include in the load balancer
4. Configure balancing strategy and health checks
#### Access Control
1. Go to `Access Control`
2. Add devices by MAC address or IP range
3. Configure proxy rules for each device
4. Set up domain and IP filtering
#### DNS Configuration
1. Go to `Basic Settings``DNS Settings`
2. Configure direct and remote DNS servers
3. Set up DNS filtering rules
4. Enable DNS over HTTPS if desired
</details>
<details>
<summary>📚 Supported Protocols</summary>
### Shadowsocks
- **Libev**: Lightweight implementation
- **Rust**: Modern, high-performance implementation
- **Plugins**: Simple-obfs, v2ray-plugin support
- **Encryption**: All standard encryption methods
### V2Ray/Xray
- **Protocols**: VMess, VLESS, Trojan, Shadowsocks
- **Transports**: TCP, mKCP, WebSocket, HTTP/2, QUIC
- **Security**: TLS, XTLS support
- **Features**: Routing, DNS,流量控制
### Trojan
- **Standard Trojan**: Basic trojan protocol
- **Trojan-Go**: Enhanced with additional features
- **TLS Support**: Full TLS certificate support
- **Obfuscation**: Built-in traffic obfuscation
### Hysteria
- **Hysteria 1**: UDP-based transport protocol
- **Hysteria 2**: Improved version with better performance
- **Obfuscation**: Built-in traffic obfuscation
- **UDP Optimization**: Optimized for poor network conditions
### Sing-Box
- **Modern Architecture**: Latest proxy technology
- **Protocol Support**: All major proxy protocols
- **Performance**: High-performance implementation
- **Features**: Advanced routing and filtering
</details>
<details>
<summary>🌐 Language Support</summary>
PassWall2 supports multiple languages:
- 🇨🇳 Chinese (Simplified/Traditional)
- 🇮🇷 Persian_farsi (soon)
Language files are located in `luci-app-passwall2/po/` directory.
</details>
<details>
<summary>🔧 Troubleshooting</summary>
### Common Issues
#### Service Won't Start
1. Check system logs: `logread | grep passwall2`
2. Verify node configuration
3. Check available memory and storage
4. Ensure required packages are installed
#### DNS Issues
1. Verify DNS server configuration
2. Check DNS filtering rules
3. Test with different DNS servers
4. Clear DNS cache if needed
#### Connection Problems
1. Test node connectivity
2. Check firewall rules
3. Verify transparent proxy settings
4. Test with different protocols
#### Performance Issues
1. Monitor system resources
2. Check node health status
3. Adjust connection limits
4. Optimize routing rules
### Debug Mode
Enable debug logging:
1. Go to `Other Settings`
2. Enable debug mode
3. Check logs in `/tmp/log/passwall2.log`
### Log Locations
- Main log: `/tmp/log/passwall2.log`
- Server log: `/tmp/log/passwall2_server.log`
- Temporary files: `/tmp/etc/passwall2_tmp/`
</details>
<details>
<summary>🙏 Acknowledgments</summary>
- OpenWrt community for the excellent platform
- V2Ray/Xray project for the core proxy technology
- All contributors and testers
- The open-source community
</details>
## 📄 License
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
---
**Note**: This software is intended for legal use only. Users are responsible for complying with all applicable laws and regulations in their jurisdiction.
## Stargazers over time
[![Stargazers over time](https://starchart.cc/Openwrt-Passwall/openwrt-passwall2.svg?variant=adaptive)](https://starchart.cc/Openwrt-Passwall/openwrt-passwall2)
@@ -87,13 +87,6 @@ if data.node.type == "Xray" then
o:value("linear")
end
o = add_option(Flag, "preproxy_enabled", translate("Preproxy") .. " " .. translate("Main switch"))
main_node = add_option(ListValue, "main_node", string.format('<a style="color:red">%s</a>', translate("Preproxy Node")), translate("Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."))
add_depends(main_node, {["preproxy_enabled"] = true})
main_node.template = appname .. "/cbi/nodes_listvalue"
main_node.group = {}
o = add_option(Flag, "fakedns", '<a style="color:#FF8C00">FakeDNS</a>' .. " " .. translate("Main switch"), translate("Use FakeDNS work in the domain that proxy.") .. "<br>" ..
translate("Suitable scenarios for let the node servers get the target domain names.") .. "<br>" ..
translate("Such as: DNS unlocking of streaming media, reducing DNS query latency, etc."))
@@ -174,70 +167,66 @@ o.remove = function(self, section)
return m:del(current_node_id, shunt_rules[section]["_fakedns_option"])
end
o = s2:option(ListValue, "_proxy_tag", string.format('<a style="color:red">%s</a>', translate("Preproxy")))
--TODO Choose any node as a pre-proxy. Instead of main node.
o.template = appname .. "/cbi/nodes_listvalue"
o.group = {"",""}
o:value("", translate("Close (Not use)"))
o:value("main", translate("Use preproxy node"))
o.cfgvalue = function(self, section)
proxy_tag_node = s2:option(ListValue, "_proxy_tag", string.format('<a style="color:red" title="%s">%s</a>',
translate("Set the node to be used as a pre-proxy.") .. "\n" .. translate("Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."),
translate("Preproxy")))
proxy_tag_node.template = appname .. "/cbi/nodes_listvalue"
proxy_tag_node.group = {""}
proxy_tag_node:value("", translate("Close (Not use)"))
proxy_tag_node.cfgvalue = function(self, section)
return m:get(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end
o.write = function(self, section, value)
proxy_tag_node.write = function(self, section, value)
return m:set(current_node_id, shunt_rules[section]["_proxy_tag_option"], value)
end
o.remove = function(self, section)
proxy_tag_node.remove = function(self, section)
return m:del(current_node_id, shunt_rules[section]["_proxy_tag_option"])
end
if data.socks_list then
for k, v in pairs(data.socks_list) do
main_node:value(v.id, v.remark)
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
if data.urltest_list then
for k, v in pairs(data.urltest_list) do
main_node:value(v.id, v.remark)
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
if data.balancing_list then
for k, v in pairs(data.balancing_list) do
main_node:value(v.id, v.remark)
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
if data.iface_list then
for k, v in pairs(data.iface_list) do
main_node:value(v.id, v.remark)
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
if data.normal_list then
for k, v in pairs(data.normal_list) do
main_node:value(v.id, v.remark)
main_node.group[#main_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
_node:value(v.id, v.remark)
_node.group[#_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
if #main_node.keylist > 0 then
main_node.default = main_node.keylist[1]
proxy_tag_node:value(v.id, v.remark)
proxy_tag_node.group[#proxy_tag_node.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
end
local footer = Template(appname .. "/include/shunt_options")
@@ -17,6 +17,15 @@ m:append(header)
m:append(Template(appname .. "/cbi/nodes_multivalue_com"))
m:append(Template(appname .. "/cbi/nodes_listvalue_com"))
groups = {}
m.uci:foreach(appname, "nodes", function(s)
if s[".name"] ~= arg[1] then
if s.group and s.group ~= "" then
groups[s.group] = true
end
end
end)
s = m:section(NamedSection, arg[1], "nodes", "")
s.addremove = false
s.dynamic = false
@@ -33,14 +42,6 @@ o.rmempty = false
o = s:option(Value, "group", translate("Group Name"))
o.default = ""
o:value("", translate("default"))
local groups = {}
m.uci:foreach(appname, "nodes", function(s)
if s[".name"] ~= arg[1] then
if s.group and s.group ~= "" then
groups[s.group] = true
end
end
end)
for k, v in pairs(groups) do
o:value(k)
end
@@ -38,6 +38,10 @@ local ss_method_list = {
local security_list = { "none", "auto", "aes-128-gcm", "chacha20-poly1305", "zero" }
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
local xray_version = api.get_app_version("xray")
o = s:option(ListValue, _n("protocol"), translate("Protocol"))
@@ -128,8 +132,14 @@ m.uci:foreach(appname, "socks", function(s)
end)
if load_balancing_options then -- [[ Load balancing Start ]]
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
o:depends({ [_n("protocol")] = "_balancing" })
o.default = "manual"
o:value("manual", translate("Manual"))
o:value("batch", translate("Batch"))
o = s:option(MultiValue, _n("balancing_node"), translate("Load balancing node list"), translate("Load balancing node list, <a target='_blank' href='https://xtls.github.io/config/routing.html#balancerobject'>document</a>"))
o:depends({ [_n("node_add_mode")] = "manual" })
o.widget = "checkbox"
o.template = appname .. "/cbi/nodes_multivalue"
o.group = {}
@@ -166,6 +176,21 @@ if load_balancing_options then -- [[ Load balancing Start ]]
end
end
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
o:depends({ [_n("node_add_mode")] = "batch" })
o.widget = "checkbox"
o:value("default", translate("default"))
for k, v in pairs(groups) do
o:value(api.UrlEncode(k), k)
end
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
o:depends({ [_n("node_add_mode")] = "batch" })
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
descrStr = descrStr .. "Conditions are joined by <code>&&</code>, and their order does not affect the result."
o.description = translate(descrStr)
o = s:option(ListValue, _n("balancingStrategy"), translate("Balancing Strategy"))
o:depends({ [_n("protocol")] = "_balancing" })
o:value("random")
@@ -517,17 +542,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
o:value("none", "none")
o:value("header-srtp", "srtp")
o:value("header-utp", "utp")
o:value("header-wechat", "wechat-video")
o:value("header-dtls", "dtls")
o:value("header-wireguard", "wireguard")
o:value("header-dns", "dns")
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "header-dns" })
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
@@ -692,7 +711,7 @@ o = s:option(Value, _n("xudp_concurrency"), translate("XUDP Mux concurrency"))
o.default = 8
o:depends({ [_n("mux")] = true })
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
--[[tcpMptcp]]
@@ -724,12 +743,22 @@ o2:depends({ [_n("chain_proxy")] = "2" })
o2.template = appname .. "/cbi/nodes_listvalue"
o2.group = {}
for k, v in pairs(nodes_list) do
if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o2:value(v.id, v.remark)
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
for k, v in pairs(socks_list) do
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
for k, e in ipairs(api.get_valid_nodes()) do
if e[".name"] ~= arg[1] then
if e.protocol ~= "_shunt" and e.protocol ~= "_iface" then
o1:value(e[".name"], e["remark"])
o1.group[#o1.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
end
if not e.protocol:find("_") then
-- Landing Node not support use special node.
o2:value(e[".name"], e["remark"])
o2.group[#o2.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
end
end
end
@@ -136,8 +136,14 @@ m.uci:foreach(appname, "socks", function(s)
end)
if load_urltest_options then -- [[ URLTest Start ]]
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
o = s:option(ListValue, _n("node_add_mode"), translate("Node Addition Method"))
o:depends({ [_n("protocol")] = "_urltest" })
o.default = "manual"
o:value("manual", translate("Manual"))
o:value("batch", translate("Batch"))
o = s:option(MultiValue, _n("urltest_node"), translate("URLTest node list"), translate("List of nodes to test, <a target='_blank' href='https://sing-box.sagernet.org/configuration/outbound/urltest'>document</a>"))
o:depends({ [_n("node_add_mode")] = "manual" })
o.widget = "checkbox"
o.template = appname .. "/cbi/nodes_multivalue"
o.group = {}
@@ -174,6 +180,21 @@ if load_urltest_options then -- [[ URLTest Start ]]
end
end
o = s:option(MultiValue, _n("node_group"), translate("Select Group"))
o:depends({ [_n("node_add_mode")] = "batch" })
o.widget = "checkbox"
o:value("default", translate("default"))
for k, v in pairs(groups) do
o:value(api.UrlEncode(k), k)
end
o = s:option(Value, _n("node_match_rule"), translate("Node Matching Rules"))
o:depends({ [_n("node_add_mode")] = "batch" })
local descrStr = "Example: <code>^A && B && !C && D$</code><br>"
descrStr = descrStr .. "This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
descrStr = descrStr .. "Conditions are joined by <code>&&</code>, and their order does not affect the result."
o.description = translate(descrStr)
o = s:option(Value, _n("urltest_url"), translate("Probe URL"))
o:depends({ [_n("protocol")] = "_urltest" })
o:value("https://cp.cloudflare.com/", "Cloudflare")
@@ -826,12 +847,22 @@ o2:depends({ [_n("chain_proxy")] = "2" })
o2.template = appname .. "/cbi/nodes_listvalue"
o2.group = {}
for k, v in pairs(nodes_list) do
if v.id ~= arg[1] and (not v.chain_proxy or v.chain_proxy == "") then
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
o2:value(v.id, v.remark)
o2.group[#o2.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
for k, v in pairs(socks_list) do
o1:value(v.id, v.remark)
o1.group[#o1.group+1] = (v.group and v.group ~= "") and v.group or translate("default")
end
for k, e in ipairs(api.get_valid_nodes()) do
if e[".name"] ~= arg[1] then
if e.protocol ~= "_shunt" and e.protocol ~= "_iface" then
o1:value(e[".name"], e["remark"])
o1.group[#o1.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
end
if not e.protocol:find("_") then
-- Landing Node not support use special node.
o2:value(e[".name"], e["remark"])
o2.group[#o2.group+1] = (e["group"] and e["group"] ~= "") and e["group"] or translate("default")
end
end
end
@@ -41,9 +41,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
o.default = 0
@@ -42,9 +42,8 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
o = s:option(Flag, _n("plugin_enabled"), translate("plugin"))
o.default = 0
@@ -64,8 +64,7 @@ o = s:option(Value, _n("timeout"), translate("Connection Timeout"))
o.datatype = "uinteger"
o.default = 300
o = s:option(ListValue, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o:value("false")
o:value("true")
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"), translate("Need node support required"))
o.default = 0
api.luci_types(arg[1], m, s, type_name, option_prefix)
@@ -18,6 +18,10 @@ local x_ss_method_list = {
"none", "plain", "aes-128-gcm", "aes-256-gcm", "chacha20-poly1305", "xchacha20-poly1305", "2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305"
}
local header_type_list = {
"none", "srtp", "utp", "wechat-video", "dtls", "wireguard", "dns"
}
-- [[ Xray ]]
s.fields["type"]:value(type_name, "Xray")
@@ -318,17 +322,11 @@ o:depends({ [_n("tcp_guise")] = "http" })
-- [[ mKCP ]]--
o = s:option(ListValue, _n("mkcp_guise"), translate("Camouflage Type"), translate('<br />none: default, no masquerade, data sent is packets with no characteristics.<br />srtp: disguised as an SRTP packet, it will be recognized as video call data (such as FaceTime).<br />utp: packets disguised as uTP will be recognized as bittorrent downloaded data.<br />wechat-video: packets disguised as WeChat video calls.<br />dtls: disguised as DTLS 1.2 packet.<br />wireguard: disguised as a WireGuard packet. (not really WireGuard protocol)<br />dns: Disguising traffic as DNS requests.'))
o:value("none", "none")
o:value("header-srtp", "srtp")
o:value("header-utp", "utp")
o:value("header-wechat", "wechat-video")
o:value("header-dtls", "dtls")
o:value("header-wireguard", "wireguard")
o:value("header-dns", "dns")
for a, t in ipairs(header_type_list) do o:value(t) end
o:depends({ [_n("transport")] = "mkcp" })
o = s:option(Value, _n("mkcp_domain"), translate("Camouflage Domain"), translate("Use it together with the DNS disguised type. You can fill in any domain."))
o:depends({ [_n("mkcp_guise")] = "header-dns" })
o:depends({ [_n("mkcp_guise")] = "dns" })
o = s:option(Value, _n("mkcp_mtu"), translate("KCP MTU"))
o.default = "1350"
@@ -368,6 +366,10 @@ o = s:option(Flag, _n("acceptProxyProtocol"), translate("acceptProxyProtocol"),
o.default = "0"
o:depends({ [_n("custom")] = false })
o = s:option(Flag, _n("tcp_fast_open"), "TCP " .. translate("Fast Open"))
o.default = "0"
o:depends({ [_n("custom")] = false })
-- [[ Fallback ]]--
o = s:option(Flag, _n("fallback"), translate("Fallback"))
o:depends({ [_n("protocol")] = "vless", [_n("transport")] = "raw" })
@@ -164,6 +164,18 @@ function base64Encode(text)
return result
end
function UrlEncode(szText)
return szText:gsub("([^%w%-_%.%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
end
function UrlDecode(szText)
return szText and szText:gsub("%+", " "):gsub("%%(%x%x)", function(h)
return string.char(tonumber(h, 16))
end) or nil
end
-- Extract the domain name and port from the URL (no IP address).
function get_domain_port_from_url(url)
local scheme, domain, port = string.match(url, "^(https?)://([%w%.%-]+):?(%d*)")
@@ -1427,3 +1439,50 @@ function apply_redirect(m)
sys.call("/bin/rm -f " .. tmp_uci_file)
end
end
function match_node_rule(name, rule)
if not name then return false end
if not rule or rule == "" then return true end
-- split rule by &&
local function split_and(expr)
local t = {}
for part in expr:gmatch("[^&]+") do
part = part:gsub("^%s+", ""):gsub("%s+$", "")
if part ~= "" then
table.insert(t, part)
end
end
return t
end
-- match single condition
local function match_cond(str, cond)
if cond == "" then
return true
end
-- exclude: !xxx
if cond:sub(1, 1) == "!" then
local k = cond:sub(2)
if k == "" then return true end
return not str:find(k, 1, true)
end
-- prefix: ^xxx
if cond:sub(1, 1) == "^" then
local k = cond:sub(2)
return str:sub(1, #k) == k
end
-- suffix: xxx$
if cond:sub(-1) == "$" then
local k = cond:sub(1, -2)
return str:sub(-#k) == k
end
-- contains
return str:find(cond, 1, true) ~= nil
end
-- AND logic
for _, cond in ipairs(split_and(rule)) do
if not match_cond(name, cond) then
return false
end
end
return true
end
@@ -71,7 +71,7 @@ function gen_config(var)
password = node.password,
method = node.method,
timeout = tonumber(node.timeout),
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false,
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false,
reuse_port = true
}
@@ -98,7 +98,7 @@ function gen_config(var)
}
},
locals = {},
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "true") and true or false
fast_open = (node.tcp_fast_open and node.tcp_fast_open == "1") and true or false
}
if local_socks_address and local_socks_port then
table.insert(config.locals, {
@@ -1095,9 +1095,10 @@ function gen_config(var)
local socks_node = uci:get_all(appname, socks_id) or nil
if socks_node then
if not remarks then
remarks = "Socks_" .. socks_node.port
remarks = socks_node.port
end
result = {
[".name"] = "Socksid_" .. socks_id,
remarks = remarks,
type = "sing-box",
protocol = "socks",
@@ -1109,7 +1110,43 @@ function gen_config(var)
return result
end
function gen_urltest(_node)
local nodes_list = {}
function get_urltest_batch_nodes(_node)
if #nodes_list == 0 then
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
nodes_list[#nodes_list + 1] = {
id = e[".name"],
remarks = e["remarks"],
group = e["group"]
}
end
end
end
if not _node.node_group or _node.node_group == "" then return {} end
local nodes = {}
for g in _node.node_group:gmatch("%S+") do
g = api.UrlDecode(g)
for k, v in pairs(nodes_list) do
local gn = (v.group and v.group ~= "") and v.group or "default"
if gn == g and api.match_node_rule(v.remarks, _node.node_match_rule) then
nodes[#nodes + 1] = v.id
end
end
end
return nodes
end
function get_node_by_id(node_id)
if not node_id or node_id == "" or node_id == "nil" then return nil end
if node_id:find("Socks_") then
return gen_socks_config_node(node_id)
else
return uci:get_all(appname, node_id)
end
end
function gen_urltest_outbound(_node)
local urltest_id = _node[".name"]
local urltest_tag = "urltest-" .. urltest_id
-- existing urltest
@@ -1119,7 +1156,13 @@ function gen_config(var)
end
end
-- new urltest
local ut_nodes = _node.urltest_node
local ut_nodes
if _node.node_add_mode and _node.node_add_mode == "batch" then
ut_nodes = get_urltest_batch_nodes(_node)
else
ut_nodes = _node.urltest_node
end
if #ut_nodes == 0 then return nil end
local valid_nodes = {}
for i = 1, #ut_nodes do
local ut_node_id = ut_nodes[i]
@@ -1133,21 +1176,9 @@ function gen_config(var)
end
end
if is_new_ut_node then
local ut_node
if ut_node_id:find("Socks_") then
ut_node = gen_socks_config_node(ut_node_id)
else
ut_node = uci:get_all(appname, ut_node_id)
end
if ut_node then
local outbound = gen_outbound(flag, ut_node, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outbound then
if ut_node.remarks then
outbound.tag = outbound.tag .. ":" .. ut_node.remarks
end
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
local outboundTag = gen_outbound_get_tag(flag, ut_node_id, ut_node_tag, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag
end
end
end
@@ -1162,8 +1193,7 @@ function gen_config(var)
idle_timeout = (api.format_go_time(_node.urltest_idle_timeout) ~= "0s") and api.format_go_time(_node.urltest_idle_timeout) or "30m",
interrupt_exist_connections = (_node.urltest_interrupt_exist_connections == "true" or _node.urltest_interrupt_exist_connections == "1") and true or false
}
table.insert(outbounds, outbound)
return urltest_tag
return outbound
end
function set_outbound_detour(node, outbound, outbounds_table, shunt_rule_name)
@@ -1197,9 +1227,16 @@ function gen_config(var)
if outbound["_flag_proxy_tag"] then
--Ignore
else
local preproxy_node = uci:get_all(appname, node.preproxy_node)
local preproxy_node = get_node_by_id(node.preproxy_node)
if preproxy_node then
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
local preproxy_outbound
if preproxy_node.protocol == "_urltest" then
if preproxy_node.urltest_node then
preproxy_outbound = gen_urltest_outbound(preproxy_node)
end
else
preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
end
if preproxy_outbound then
preproxy_outbound.tag = preproxy_node[".name"]
if preproxy_node.remarks then
@@ -1214,7 +1251,13 @@ function gen_config(var)
end
end
if node.chain_proxy == "2" and node.to_node then
local to_node = uci:get_all(appname, node.to_node)
local to_node = get_node_by_id(node.to_node)
if to_node then
-- Landing Node not support use special node.
if to_node.protocol:find("_") then
to_node = nil
end
end
if to_node then
local to_outbound
if to_node.type ~= "sing-box" then
@@ -1265,122 +1308,90 @@ function gen_config(var)
return default_outTag, last_insert_outbound
end
function gen_outbound_get_tag(flag, node_id, tag, proxy_table)
if not node_id or node_id == "nil" then return nil end
local node
if type(node_id) == "string" then
node = get_node_by_id(node_id)
elseif type(node_id) == "table" then
node = node_id
end
if node then
if node.protocol == "_iface" then
if node.iface then
local outbound = {
tag = tag,
type = "direct",
bind_interface = node.iface,
routing_mark = 255,
}
table.insert(outbounds, outbound)
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
return outbound.tag
end
return nil
end
if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then
node.chain_proxy = proxy_table.chain_proxy
node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node
node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node
proxy_table.chain_proxy = nil
proxy_table.preproxy_node = nil
proxy_table.to_node = nil
end
local outbound
if node.protocol == "_urltest" then
if node.urltest_node then
outbound = gen_urltest_outbound(node)
end
else
outbound = gen_outbound(flag, node, tag, proxy_table)
end
if outbound then
if node.remarks then
outbound.tag = outbound.tag .. ":" .. node.remarks
end
local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
table.insert(outbounds, outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
return default_outbound_tag
end
end
end
rules = {}
if node.protocol == "_shunt" then
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
local preproxy_tag = preproxy_rule_name
local preproxy_node_id = preproxy_rule_name and node["main_node"] or nil
inner_fakedns = node.fakedns or "0"
local function gen_shunt_node(rule_name, _node_id)
if not rule_name then return nil end
if not _node_id then _node_id = node[rule_name] end
local rule_outboundTag
if _node_id == "_direct" then
rule_outboundTag = "direct"
return "direct"
elseif _node_id == "_blackhole" then
rule_outboundTag = "block"
return "block"
elseif _node_id == "_default" and rule_name ~= "default" then
rule_outboundTag = "default"
elseif _node_id and _node_id:find("Socks_") then
local socks_node = gen_socks_config_node(_node_id)
local _outbound = gen_outbound(flag, socks_node, rule_name)
if _outbound then
table.insert(outbounds, _outbound)
rule_outboundTag = _outbound.tag
end
return "default"
elseif _node_id then
local _node = uci:get_all(appname, _node_id)
if not _node then return nil end
if api.is_normal_node(_node) then
local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
local copied_outbound
for index, value in ipairs(outbounds) do
if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
copied_outbound = api.clone(value)
break
end
end
if copied_outbound then
copied_outbound.tag = rule_name .. ":" .. _node.remarks
table.insert(outbounds, copied_outbound)
rule_outboundTag = copied_outbound.tag
else
if use_proxy then
local pre_proxy = nil
if _node.type ~= "sing-box" then
pre_proxy = true
end
if pre_proxy then
local new_port = api.get_new_port()
table.insert(inbounds, {
type = "direct",
tag = "proxy_" .. rule_name,
listen = "127.0.0.1",
listen_port = new_port,
override_address = _node.address,
override_port = tonumber(_node.port),
})
if _node.tls_serverName == nil then
_node.tls_serverName = _node.address
end
_node.address = "127.0.0.1"
_node.port = new_port
table.insert(rules, 1, {
inbound = {"proxy_" .. rule_name},
outbound = preproxy_tag,
})
end
end
local proxy_table = {
tag = use_proxy and preproxy_tag or nil,
run_socks_instance = not no_run
}
if not proxy_table.tag then
if singbox_settings.fragment == "1" then
proxy_table.fragment = true
end
if singbox_settings.record_fragment == "1" then
proxy_table.record_fragment = true
end
end
local _outbound = gen_outbound(flag, _node, rule_name, proxy_table)
if _outbound then
_outbound.tag = _outbound.tag .. ":" .. _node.remarks
rule_outboundTag, last_insert_outbound = set_outbound_detour(_node, _outbound, outbounds, rule_name)
table.insert(outbounds, _outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
end
end
elseif _node.protocol == "_urltest" then
rule_outboundTag = gen_urltest(_node)
elseif _node.protocol == "_iface" then
if _node.iface then
local _outbound = {
type = "direct",
tag = rule_name .. ":" .. _node.remarks,
bind_interface = _node.iface,
routing_mark = 255,
}
table.insert(outbounds, _outbound)
rule_outboundTag = _outbound.tag
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
end
local proxy_table = {
fragment = singbox_settings.fragment == "1",
record_fragment = singbox_settings.record_fragment == "1",
run_socks_instance = not no_run,
}
local preproxy_node_id = node[rule_name .. "_proxy_tag"]
if preproxy_node_id == _node_id then preproxy_node_id = nil end
if preproxy_node_id then
proxy_table.chain_proxy = "2"
proxy_table.to_node = _node_id
return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table)
else
return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table)
end
end
return rule_outboundTag
end
if preproxy_tag and preproxy_node_id then
local preproxy_outboundTag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
if preproxy_outboundTag then
preproxy_tag = preproxy_outboundTag
end
return nil
end
--default_node
@@ -1582,32 +1593,12 @@ function gen_config(var)
table.insert(rules, rule)
end
end)
elseif node.protocol == "_urltest" then
if node.urltest_node then
COMMON.default_outbound_tag = gen_urltest(node)
end
elseif node.protocol == "_iface" then
if node.iface then
local outbound = {
type = "direct",
tag = node.remarks or node_id,
bind_interface = node.iface,
routing_mark = 255,
}
table.insert(outbounds, outbound)
COMMON.default_outbound_tag = outbound.tag
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end
else
local outbound = gen_outbound(flag, node, nil, { fragment = singbox_settings.fragment == "1" or nil, record_fragment = singbox_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outbound then
outbound.tag = outbound.tag .. ":" .. node.remarks
COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
table.insert(outbounds, outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
end
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = singbox_settings.fragment == "1" or nil,
record_fragment = singbox_settings.record_fragment == "1" or nil,
run_socks_instance = not no_run
})
end
for index, value in ipairs(rules) do
@@ -1730,7 +1721,7 @@ function gen_config(var)
local default_dns_flag = "remote"
if node_id and redir_port then
local node = uci:get_all(appname, node_id)
local node = get_node_by_id(node_id)
if node.protocol == "_shunt" then
if node.default_node == "_direct" then
default_dns_flag = "direct"
@@ -58,7 +58,7 @@ function gen_outbound(flag, node, tag, proxy_table)
run_socks_instance = proxy_table.run_socks_instance
end
if node.type ~= "Xray" then
if node.type ~= "Xray" or node.protocol == "_balancing" then
local relay_port = node.port
local new_port = api.get_new_port()
local config_file = string.format("%s_%s_%s.json", flag, tag, new_port)
@@ -282,9 +282,11 @@ function gen_outbound(flag, node, tag, proxy_table)
finalmask = (node.transport == "mkcp") and {
udp = (function()
local t = {}
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
if node.mkcp_guise and node.mkcp_guise ~= "none" then
local g = { type = node.mkcp_guise }
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
local g = { type = map[node.mkcp_guise] }
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
g.settings = { domain = node.mkcp_domain }
end
t[#t + 1] = g
@@ -592,9 +594,11 @@ function gen_config_server(node)
finalmask = (node.transport == "mkcp") and {
udp = (function()
local t = {}
local map = {none = "none", srtp = "header-srtp", utp = "header-utp", ["wechat-video"] = "header-wechat",
dtls = "header-dtls", wireguard = "header-wireguard", dns = "header-dns"}
if node.mkcp_guise and node.mkcp_guise ~= "none" then
local g = { type = node.mkcp_guise }
if node.mkcp_guise == "header-dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
local g = { type = map[node.mkcp_guise] }
if node.mkcp_guise == "dns" and node.mkcp_domain and node.mkcp_domain ~= "" then
g.settings = { domain = node.mkcp_domain }
end
t[#t + 1] = g
@@ -608,6 +612,7 @@ function gen_config_server(node)
end)()
} or nil,
sockopt = {
tcpFastOpen = (node.tcp_fast_open == "1") and true or nil,
acceptProxyProtocol = (node.acceptProxyProtocol and node.acceptProxyProtocol == "1") and true or false
}
}
@@ -757,9 +762,10 @@ function gen_config(var)
local socks_node = uci:get_all(appname, socks_id) or nil
if socks_node then
if not remarks then
remarks = "Socks_" .. socks_node.port
remarks = socks_node.port
end
result = {
[".name"] = "Socksid_" .. socks_id,
remarks = remarks,
type = "Xray",
protocol = "socks",
@@ -772,15 +778,51 @@ function gen_config(var)
return result
end
function gen_loopback(outboundTag, dst_node_id)
if not outboundTag then return nil end
local inboundTag = dst_node_id and "loop-in-" .. dst_node_id or outboundTag .. "-lo"
function get_node_by_id(node_id)
if not node_id or node_id == "" or node_id == "nil" then return nil end
if node_id:find("Socks_") then
return gen_socks_config_node(node_id)
else
return uci:get_all(appname, node_id)
end
end
local nodes_list = {}
function get_balancer_batch_nodes(_node)
if #nodes_list == 0 then
for k, e in ipairs(api.get_valid_nodes()) do
if e.node_type == "normal" and (not e.chain_proxy or e.chain_proxy == "") then
nodes_list[#nodes_list + 1] = {
id = e[".name"],
remarks = e["remarks"],
group = e["group"]
}
end
end
end
if not _node.node_group or _node.node_group == "" then return {} end
local nodes = {}
for g in _node.node_group:gmatch("%S+") do
g = api.UrlDecode(g)
for k, v in pairs(nodes_list) do
local gn = (v.group and v.group ~= "") and v.group or "default"
if gn == g and api.match_node_rule(v.remarks, _node.node_match_rule) then
nodes[#nodes + 1] = v.id
end
end
end
return nodes
end
function gen_loopback(outbound_tag, loopback_dst)
if not outbound_tag or outbound_tag == "" then return nil end
local inbound_tag = loopback_dst and "lo-to-" .. loopback_dst or outbound_tag .. "-lo"
table.insert(outbounds, {
protocol = "loopback",
tag = outboundTag,
settings = { inboundTag = inboundTag }
tag = outbound_tag,
settings = { inboundTag = inbound_tag }
})
return inboundTag
return inbound_tag
end
function gen_balancer(_node, loopback_tag)
@@ -796,7 +838,12 @@ function gen_config(var)
end
end
-- new balancer
local blc_nodes = _node.balancing_node
local blc_nodes
if _node.node_add_mode and _node.node_add_mode == "batch" then
blc_nodes = get_balancer_batch_nodes(_node)
else
blc_nodes = _node.balancing_node
end
local valid_nodes = {}
for i = 1, #blc_nodes do
local blc_node_id = blc_nodes[i]
@@ -810,21 +857,9 @@ function gen_config(var)
end
end
if is_new_blc_node then
local blc_node
if blc_node_id:find("Socks_") then
blc_node = gen_socks_config_node(blc_node_id)
else
blc_node = uci:get_all(appname, blc_node_id)
end
if blc_node then
local outbound = gen_outbound(flag, blc_node, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outbound then
if blc_node.remarks then
outbound.tag = outbound.tag .. ":" .. blc_node.remarks
end
table.insert(outbounds, outbound)
valid_nodes[#valid_nodes + 1] = outbound.tag
end
local outboundTag = gen_outbound_get_tag(flag, blc_node_id, blc_node_tag, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outboundTag then
valid_nodes[#valid_nodes + 1] = outboundTag
end
end
end
@@ -844,21 +879,12 @@ function gen_config(var)
end
end
if is_new_node then
local fallback_node
if fallback_node_id:find("Socks_") then
fallback_node = gen_socks_config_node(fallback_node_id)
else
fallback_node = uci:get_all(appname, fallback_node_id)
end
local fallback_node = get_node_by_id(fallback_node_id)
if fallback_node then
if fallback_node.protocol ~= "_balancing" then
local outbound = gen_outbound(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outbound then
if fallback_node.remarks then
outbound.tag = outbound.tag .. ":" .. fallback_node.remarks
end
table.insert(outbounds, outbound)
fallback_node_tag = outbound.tag
local outboundTag = gen_outbound_get_tag(flag, fallback_node, fallback_node_id, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.record_fragment == "1" or nil, run_socks_instance = not no_run })
if outboundTag then
fallback_node_tag = outboundTag
end
else
if gen_balancer(fallback_node) then
@@ -923,7 +949,7 @@ function gen_config(var)
if outbound["_flag_proxy_tag"] then
--Ignore
else
local preproxy_node = uci:get_all(appname, node.preproxy_node)
local preproxy_node = get_node_by_id(node.preproxy_node)
if preproxy_node then
local preproxy_outbound = gen_outbound(node[".name"], preproxy_node)
if preproxy_outbound then
@@ -943,7 +969,13 @@ function gen_config(var)
end
end
if node.chain_proxy == "2" and node.to_node then
local to_node = uci:get_all(appname, node.to_node)
local to_node = get_node_by_id(node.to_node)
if to_node then
-- Landing Node not support use special node.
if to_node.protocol:find("_") then
to_node = nil
end
end
if to_node then
local to_outbound
if to_node.type ~= "Xray" then
@@ -996,18 +1028,66 @@ function gen_config(var)
return default_outTag, last_insert_outbound
end
function gen_outbound_get_tag(flag, node_id, tag, proxy_table)
if not node_id or node_id == "nil" then return nil end
local node
if type(node_id) == "string" then
node = get_node_by_id(node_id)
elseif type(node_id) == "table" then
node = node_id
end
if node then
if node.protocol == "_iface" then
if node.iface then
local outbound = {
tag = tag,
protocol = "freedom",
streamSettings = {
sockopt = {
mark = 255,
interface = node.iface
}
}
}
table.insert(outbounds, outbound)
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
return outbound.tag
end
return nil
end
if proxy_table.chain_proxy == "1" or proxy_table.chain_proxy == "2" then
node.chain_proxy = proxy_table.chain_proxy
node.preproxy_node = proxy_table.chain_proxy == "1" and proxy_table.preproxy_node
node.to_node = proxy_table.chain_proxy == "2" and proxy_table.to_node
proxy_table.chain_proxy = nil
proxy_table.preproxy_node = nil
proxy_table.to_node = nil
end
local outbound = gen_outbound(flag, node, tag, proxy_table)
if outbound then
if node.remarks then
outbound.tag = outbound.tag .. ":" .. node.remarks
end
local default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
if tag == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
return default_outbound_tag
end
end
end
if node then
if server_host and server_port then
node.address = server_host
node.port = server_port
end
if node.protocol == "_shunt" then
local preproxy_rule_name = node.preproxy_enabled == "1" and "main" or nil
local preproxy_tag = preproxy_rule_name
local preproxy_node_id = node["main_node"]
local preproxy_outbound_tag, preproxy_balancer_tag
local preproxy_nodes
inner_fakedns = node.fakedns or "0"
local function gen_shunt_node(rule_name, _node_id)
@@ -1017,144 +1097,25 @@ function gen_config(var)
return "direct", nil
elseif _node_id == "_blackhole" then
return "blackhole", nil
elseif _node_id == "_default" then
elseif _node_id == "_default" and rule_name ~= "default" then
return "default", nil
elseif _node_id and _node_id:find("Socks_") then
local socks_tag
local socks_node = gen_socks_config_node(_node_id)
local outbound = gen_outbound(flag, socks_node, rule_name)
if outbound then
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
socks_tag = outbound.tag
end
return socks_tag, nil
elseif _node_id then
local _node = uci:get_all(appname, _node_id)
if not _node then return nil, nil end
if api.is_normal_node(_node) then
local use_proxy = preproxy_tag and node[rule_name .. "_proxy_tag"] == preproxy_rule_name and _node_id ~= preproxy_node_id
if use_proxy and preproxy_balancer_tag and preproxy_nodes[_node_id] then use_proxy = false end
local copied_outbound
for index, value in ipairs(outbounds) do
if value["_id"] == _node_id and value["_flag_proxy_tag"] == (use_proxy and preproxy_tag or nil) then
copied_outbound = api.clone(value)
break
end
end
if copied_outbound then
copied_outbound.tag = rule_name .. ":" .. _node.remarks
table.insert(outbounds, copied_outbound)
return copied_outbound.tag, nil
else
if use_proxy and _node.type ~= "Xray" then
local new_port = api.get_new_port()
table.insert(inbounds, {
tag = "proxy_" .. rule_name,
listen = "127.0.0.1",
port = new_port,
protocol = "dokodemo-door",
settings = {network = "tcp,udp", address = _node.address, port = tonumber(_node.port)}
})
if _node.tls_serverName == nil then
_node.tls_serverName = _node.address
end
_node.address = "127.0.0.1"
_node.port = new_port
table.insert(rules, 1, {
inboundTag = {"proxy_" .. rule_name},
outboundTag = not preproxy_balancer_tag and preproxy_tag or nil,
balancerTag = preproxy_balancer_tag
})
end
local proxy_table = {
tag = use_proxy and preproxy_tag or nil,
run_socks_instance = not no_run
}
if not proxy_table.tag then
if xray_settings.fragment == "1" then
proxy_table.fragment = true
end
if xray_settings.noise == "1" then
proxy_table.noise = true
end
end
local outbound = gen_outbound(flag, _node, rule_name, proxy_table)
local outbound_tag
if outbound then
outbound.tag = outbound.tag .. ":" .. _node.remarks
outbound_tag, last_insert_outbound = set_outbound_detour(_node, outbound, outbounds, rule_name)
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
end
return outbound_tag, nil
end
elseif _node.protocol == "_balancing" then
local blc_tag = gen_balancer(_node, rule_name)
if rule_name == "default" then
for i, ob in ipairs(outbounds) do
if ob.protocol == "loopback" and ob.tag == "default" then
if i > 1 then table.insert(outbounds, 1, table.remove(outbounds, i)) end
break
end
end
end
return nil, blc_tag
elseif _node.protocol == "_iface" then
local outbound_tag
if _node.iface then
local outbound = {
protocol = "freedom",
tag = rule_name,
streamSettings = {
sockopt = {
mark = 255,
interface = _node.iface
}
}
}
outbound_tag = outbound.tag
if rule_name == "default" then
table.insert(outbounds, 1, outbound)
else
table.insert(outbounds, outbound)
end
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, _node.iface))
end
return outbound_tag, nil
local proxy_table = {
fragment = xray_settings.fragment == "1",
noise = xray_settings.noise == "1",
run_socks_instance = not no_run,
}
local preproxy_node_id = node[rule_name .. "_proxy_tag"]
if preproxy_node_id == _node_id then preproxy_node_id = nil end
if preproxy_node_id then
proxy_table.chain_proxy = "2"
proxy_table.to_node = _node_id
return gen_outbound_get_tag(flag, preproxy_node_id, rule_name, proxy_table), nil
else
return gen_outbound_get_tag(flag, _node_id, rule_name, proxy_table), nil
end
end
end
if preproxy_tag and preproxy_node_id then
preproxy_outbound_tag, preproxy_balancer_tag = gen_shunt_node(preproxy_rule_name, preproxy_node_id)
if preproxy_balancer_tag then
local _node_id = preproxy_node_id
preproxy_nodes = {}
while _node_id do
_node = uci:get_all(appname, _node_id)
if not _node then break end
if _node.protocol ~= "_balancing" then
preproxy_nodes[_node_id] = true
break
end
local _blc_nodes = _node.balancing_node
for i = 1, #_blc_nodes do preproxy_nodes[_blc_nodes[i]] = true end
_node_id = _node.fallback_node
end
elseif preproxy_outbound_tag then
preproxy_tag = preproxy_outbound_tag
end
return nil, nil
end
--default_node
@@ -1290,7 +1251,13 @@ function gen_config(var)
rules = rules
}
elseif node.protocol == "_balancing" then
if node.balancing_node then
local blc_nodes
if node.node_add_mode and node.node_add_mode == "batch" then
blc_nodes = get_balancer_batch_nodes(node)
else
blc_nodes = node.balancing_node
end
if blc_nodes and #blc_nodes > 0 then
local balancer_tag = gen_balancer(node)
if balancer_tag then
table.insert(rules, { network = "tcp,udp", balancerTag = balancer_tag })
@@ -1301,31 +1268,13 @@ function gen_config(var)
}
COMMON.default_balancer_tag = balancer_tag
end
elseif node.protocol == "_iface" then
if node.iface then
local outbound = {
protocol = "freedom",
tag = node.remarks or node_id,
streamSettings = {
sockopt = {
mark = 255,
interface = node.iface
}
}
}
table.insert(outbounds, outbound)
COMMON.default_outbound_tag = outbound.tag
sys.call(string.format("mkdir -p %s && touch %s/%s", api.TMP_IFACE_PATH, api.TMP_IFACE_PATH, node.iface))
end
else
local outbound = gen_outbound(flag, node, nil, { fragment = xray_settings.fragment == "1" or nil, noise = xray_settings.noise == "1" or nil, run_socks_instance = not no_run })
if outbound then
outbound.tag = outbound.tag .. ":" .. node.remarks
COMMON.default_outbound_tag, last_insert_outbound = set_outbound_detour(node, outbound, outbounds)
table.insert(outbounds, outbound)
if last_insert_outbound then
table.insert(outbounds, last_insert_outbound)
end
COMMON.default_outbound_tag = gen_outbound_get_tag(flag, node, nil, {
fragment = xray_settings.fragment == "1" or nil,
noise = xray_settings.noise == "1" or nil,
run_socks_instance = not no_run
})
if COMMON.default_outbound_tag then
routing = {
domainStrategy = "AsIs",
domainMatcher = "hybrid",
@@ -239,6 +239,9 @@ local current_node = map:get(section)
}
var params = "";
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
var v_plugin_dom = opt.get(dom_prefix + "plugin");
if (v_plugin_dom) {
var v_plugin = v_plugin_dom.value;
@@ -318,6 +321,8 @@ local current_node = map:get(section)
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "uot")?.checked && (params += "&udp=1");
if (opt.get(dom_prefix + "shadowtls")?.checked) {
let st_plugin_str = "";
let st_version = opt.get(dom_prefix + "shadowtls_version")?.value;
@@ -414,6 +419,9 @@ local current_node = map:get(section)
info.tls = "tls";
info.sni = opt.get(dom_prefix + "tls_serverName").value;
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (info.tfo = "1");
url = b64EncodeUnicode(JSON.stringify(info));
} else if ((v_type === "sing-box" || v_type === "Xray") && opt.get(dom_prefix + "protocol").value === "vless") {
protocol = "vless";
@@ -498,6 +506,8 @@ local current_node = map:get(section)
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
params = params.substring(1);
@@ -563,6 +573,9 @@ local current_node = map:get(section)
params += opt.query("vcn", dom_prefix + "tls_CertByName");
params += opt.query("ech", dom_prefix + "ech_config");
}
opt.get(dom_prefix + "tcp_fast_open")?.checked && (params += "&tfo=1");
params += "#" + encodeURI(v_alias.value);
if (params[0] == "&") {
params = params.substring(1);
@@ -940,11 +953,14 @@ local current_node = map:get(section)
pluginOpts = pluginParams.join(";");
}
if (has_xray && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "xray" || queryParam.type == "xhttp")) {
const needUpgrade = ss_type !== "Xray" && ss_type !== "sing-box" &&
queryParam.type && queryParam.type !== "tcp" &&
queryParam.headerType && queryParam.headerType !== "none";
if (has_xray && (ss_type == "xray" || needUpgrade || queryParam.type === "xhttp")) {
dom_prefix = "xray_"
opt.set('type', "Xray");
opt.set(dom_prefix + 'protocol', "shadowsocks");
} else if (has_singbox && ((ss_type !== "xray" && ss_type !== "sing-box" && queryParam.type) || ss_type == "sing-box")) {
} else if (has_singbox && (ss_type == "sing-box" || needUpgrade)) {
dom_prefix = "singbox_"
opt.set('type', "sing-box");
opt.set(dom_prefix + 'protocol', "shadowsocks");
@@ -973,6 +989,7 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'password', password || "");
opt.set(dom_prefix + 'method', method || "");
opt.set(dom_prefix + 'ss_method', method || "");
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (plugin && plugin != "none") {
plugin = (plugin === "simple-obfs") ? "obfs-local" : plugin;
opt.set(dom_prefix + 'plugin_enabled', true);
@@ -1110,6 +1127,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'use_xhttp_extra', !!queryParam.extra);
opt.set(dom_prefix + 'xhttp_extra', queryParam.extra || "");
}
opt.set(dom_prefix + 'uot', queryParam.udp);
if (queryParam["shadow-tls"]) {
// Parsing SS Shadow-TLS plugin parameters
@@ -1267,7 +1286,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'tls_allowInsecure', false);
}
opt.set(dom_prefix + 'mux', queryParam.mux === '1');
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
}
@@ -1362,6 +1382,8 @@ local current_node = map:get(section)
} else if (ssm.net === "grpc") {
opt.set(dom_prefix + 'grpc_serviceName', ssm.path);
}
opt.set(dom_prefix + 'tcp_fast_open', ssm.tfo);
}
if (ssu[0] === "vless") {
if (vless_type == "sing-box" && has_singbox) {
@@ -1513,6 +1535,8 @@ local current_node = map:get(section)
opt.set(dom_prefix + 'httpupgrade_path', queryParam.path || "");
}
opt.set(dom_prefix + 'tcp_fast_open', queryParam.tfo);
if (m.hash) {
opt.set('remarks', decodeURIComponent(m.hash.substr(1)));
}
@@ -1692,12 +1716,14 @@ local current_node = map:get(section)
}
document.addEventListener("DOMContentLoaded", function () {
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
} else {
sessionStorage.removeItem("fromUrl");
}
setTimeout(function () {
const fromUrlCache = JSON.parse(sessionStorage.getItem("fromUrl"));
if (fromUrlCache && fromUrlCache.savetime && (Date.now() - fromUrlCache.timestamp) < fromUrlCache.savetime) {
fromUrl(null, fromUrlCache.urlname, fromUrlCache.sid, fromUrlCache)
} else {
sessionStorage.removeItem("fromUrl");
}
}, 500);
})
//]]></script>
@@ -453,14 +453,11 @@ msgstr "پیش‌پروکسی"
msgid "Preproxy Node"
msgstr "گره پیش‌پروکسی"
msgid ""
"Set the node to be used as a pre-proxy. Each rule (including <code>Default</"
"code>) has a separate switch that controls whether this rule uses the pre-"
"proxy or not."
msgstr ""
"گره‌ای را که باید به عنوان پیش‌پروکسی استفاده شود تنظیم کنید. هر قانون (شامل <code>Default</"
"code>) دارای یک سوئیچ جداگانه است که کنترل می‌کند آیا این قانون از پیش-"
"پروکسی استفاده کند یا خیر."
msgid "Set the node to be used as a pre-proxy."
msgstr "گره را طوری تنظیم کنید که به عنوان پیش پروکسی استفاده شود."
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "هر قانون یک سوئیچ جداگانه دارد که کنترل می‌کند آیا این قانون از پیش-پروکسی استفاده می‌کند یا خیر."
msgid "Close (Not use)"
msgstr "بستن (عدم استفاده)"
@@ -474,12 +471,6 @@ msgstr "اتصال مستقیم"
msgid "Blackhole (Block)"
msgstr "سیاه‌چاله (مسدودسازی)"
msgid "Use preproxy node"
msgstr "استفاده از گره پیش‌پروکسی"
msgid "Default Preproxy"
msgstr "پیش‌پروکسی پیش‌فرض"
msgid "There are no available nodes, please add or subscribe nodes first."
msgstr "هیچ گره در دسترس نیست، لطفاً ابتدا گره‌ها را اضافه یا مشترک شوید."
@@ -813,18 +804,6 @@ msgstr "سوئیچ بازگردانی"
msgid "When detects main node is available, switch back to the main node."
msgstr "زمانی که در دسترس بودن گره اصلی تشخیص داده شود، به گره اصلی بازگردانده می‌شود."
msgid "If the main node is shunt"
msgstr "اگر گره اصلی شنت (Shunt) باشد"
msgid "Switch it"
msgstr "سوئیچ کردن"
msgid "Applying to the default node"
msgstr "اعمال بر روی گره پیش‌فرض"
msgid "Applying to the default preproxy node"
msgstr "اعمال بر روی گره پیش‌پروکسی پیش‌فرض"
msgid "Add nodes to the standby node list by keywords"
msgstr "افزودن گره‌ها به لیست گره‌های آماده‌به‌کار با استفاده از کلمات کلیدی"
@@ -367,6 +367,30 @@ msgstr "分流"
msgid "Balancing"
msgstr "负载均衡"
msgid "Node Addition Method"
msgstr "节点添加方式"
msgid "Manual"
msgstr "手动"
msgid "Batch"
msgstr "批量"
msgid "Select Group"
msgstr "选择分组"
msgid "Node Matching Rules"
msgstr "节点匹配规则"
msgid ""
"Example: <code>^A && B && !C && D$</code><br>"
"This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
"Conditions are joined by <code>&&</code>, and their order does not affect the result."
msgstr ""
"示例:<code>^A && B && !C && D$</code><br>"
"表示节点备注需同时满足:以 A 开头(^)、包含 B、不包含 C(!)、并以 D 结尾($)。<br>"
"多个条件使用 <code>&&</code> 连接,条件顺序不影响结果。"
msgid "Balancing Strategy"
msgstr "负载均衡策略"
@@ -412,8 +436,11 @@ msgstr "前置代理"
msgid "Preproxy Node"
msgstr "前置代理节点"
msgid "Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "设置用作前置代理的节点。每条规则(包括<code>默认</code>)都有独立开关控制本规则是否使用前置代理。"
msgid "Set the node to be used as a pre-proxy."
msgstr "设置用作前置代理的节点。"
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "每条规则都有独立开关控制本规则是否使用前置代理。"
msgid "Close (Not use)"
msgstr "关闭 (不使用)"
@@ -427,12 +454,6 @@ msgstr "直连"
msgid "Blackhole (Block)"
msgstr "黑洞(屏蔽)"
msgid "Use preproxy node"
msgstr "使用前置代理节点"
msgid "Default Preproxy"
msgstr "默认前置代理"
msgid "There are no available nodes, please add or subscribe nodes first."
msgstr "没有可用节点,请先添加或订阅节点。"
@@ -748,18 +769,6 @@ msgstr "恢复切换"
msgid "When detects main node is available, switch back to the main node."
msgstr "当检测到主节点可用时,切换回主节点。"
msgid "If the main node is shunt"
msgstr "如果主节点是分流"
msgid "Switch it"
msgstr "切掉它"
msgid "Applying to the default node"
msgstr "应用于默认节点"
msgid "Applying to the default preproxy node"
msgstr "应用于默认前置节点"
msgid "Add nodes to the standby node list by keywords"
msgstr "通过关键字添加节点到备用节点列表"
@@ -367,6 +367,30 @@ msgstr "分流"
msgid "Balancing"
msgstr "負載均衡"
msgid "Node Addition Method"
msgstr "節點新增方式"
msgid "Manual"
msgstr "手動"
msgid "Batch"
msgstr "批量"
msgid "Select Group"
msgstr "選擇分組"
msgid "Node Matching Rules"
msgstr "節點匹配規則"
msgid ""
"Example: <code>^A && B && !C && D$</code><br>"
"This means the node remark must start with A (^), include B, exclude C (!), and end with D ($).<br>"
"Conditions are joined by <code>&&</code>, and their order does not affect the result."
msgstr ""
"示例:<code>^A && B && !C && D$</code><br>"
"表示節點備註需同時滿足:以 A 開頭(^)、包含 B、不包含 C(!)、並以 D 結尾($)。<br>"
"多個條件使用 <code>&&</code> 連接,條件順序不影響結果。"
msgid "Balancing Strategy"
msgstr "負載均衡策略"
@@ -412,8 +436,11 @@ msgstr "前置代理"
msgid "Preproxy Node"
msgstr "前置代理節點"
msgid "Set the node to be used as a pre-proxy. Each rule (including <code>Default</code>) has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "設置用作前置代理的節點。每条規則(包括<code>默認</code>)都有独立開關控制本規則是否使用前置代理。"
msgid "Set the node to be used as a pre-proxy."
msgstr "設置用作前置代理的節點。"
msgid "Each rule has a separate switch that controls whether this rule uses the pre-proxy or not."
msgstr "每条規則都有独立開關控制本規則是否使用前置代理。"
msgid "Close (Not use)"
msgstr "關閉 (不使用)"
@@ -427,12 +454,6 @@ msgstr "直連"
msgid "Blackhole (Block)"
msgstr "黑洞(屏蔽)"
msgid "Use preproxy node"
msgstr "使用前置代理節點"
msgid "Default Preproxy"
msgstr "默認前置代理"
msgid "There are no available nodes, please add or subscribe nodes first."
msgstr "沒有可用節點,請先添加或訂閱節點。"
@@ -748,18 +769,6 @@ msgstr "恢復切換"
msgid "When detects main node is available, switch back to the main node."
msgstr "當檢測到主節點可用時,切換回主節點。"
msgid "If the main node is shunt"
msgstr "如果主節點是分流"
msgid "Switch it"
msgstr "切掉它"
msgid "Applying to the default node"
msgstr "應用於默認節點"
msgid "Applying to the default preproxy node"
msgstr "應用於默認前置節點"
msgid "Add nodes to the standby node list by keywords"
msgstr "通過關键字添加節點到備用節點列表"
@@ -17,6 +17,8 @@ local ssub, slen, schar, sbyte, sformat, sgsub = string.sub, string.len, string.
local split = api.split
local jsonParse, jsonStringify = luci.jsonc.parse, luci.jsonc.stringify
local base64Decode = api.base64Decode
local UrlEncode = api.UrlEncode
local UrlDecode = api.UrlDecode
local uci = api.uci
local fs = api.fs
local log = api.log
@@ -247,10 +249,6 @@ do
[".name"] = "default_node",
remarks = i18n.translatef("Default")
})
table.insert(rules, {
[".name"] = "main_node",
remarks = i18n.translatef("Default Preproxy")
})
for k, e in pairs(rules) do
local _node_id = node[e[".name"]] or nil
@@ -403,18 +401,6 @@ do
end
end
local function UrlEncode(szText)
return szText:gsub("([^%w%-_%.%~])", function(c)
return string.format("%%%02X", string.byte(c))
end)
end
local function UrlDecode(szText)
return szText and szText:gsub("+", " "):gsub("%%(%x%x)", function(h)
return string.char(tonumber(h, 16))
end) or nil
end
-- Retrieve subscribe information (remaining data allowance, expiration time).
local subscribe_info = {}
local function get_subscribe_info(cfgid, value)
@@ -617,6 +603,8 @@ local function processData(szType, content, add_mode, group)
result.tls = "0"
end
result.tcp_fast_open = info.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil
@@ -719,13 +707,17 @@ local function processData(szType, content, add_mode, group)
result.method = method
result.password = password
result.tcp_fast_open = params.tfo
if has_xray and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
result.type = 'Xray'
result.protocol = 'shadowsocks'
elseif has_singbox and (result.type ~= 'Xray' and result.type ~= 'sing-box' and params.type) then
result.type = 'sing-box'
result.protocol = 'shadowsocks'
local need_upgrade = (result.type ~= "Xray" and result.type ~= "sing-box")
and (params.type and params.type ~= "tcp")
and (params.headerType and params.headerType ~= "none")
if has_xray and (need_upgrade or params.type == "xhttp") then
result.type = "Xray"
result.protocol = "shadowsocks"
elseif has_singbox and need_upgrade then
result.type = "sing-box"
result.protocol = "shadowsocks"
end
if result.plugin then
@@ -888,7 +880,8 @@ local function processData(szType, content, add_mode, group)
else
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
else
result.uot = params.udp
elseif (params.type ~= "tcp" and params.type ~= "raw") and (params.headerType and params.headerType ~= "none") then
result.error_msg = i18n.translatef("Please replace Xray or Sing-Box to support more transmission methods in Shadowsocks.")
end
end
@@ -1090,6 +1083,7 @@ local function processData(szType, content, add_mode, group)
end
result.alpn = params.alpn
result.tcp_fast_open = params.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
@@ -1278,6 +1272,8 @@ local function processData(szType, content, add_mode, group)
result.tls_allowInsecure = allowInsecure_default and "1" or "0"
end
result.tcp_fast_open = params.tfo
if result.type == "sing-box" and (result.transport == "mkcp" or result.transport == "xhttp") then
log(2, i18n.translatef("Skip node: %s. Because Sing-Box does not support the %s protocol's %s transmission method, Xray needs to be used instead.", result.remarks, szType, result.transport))
return nil
+124
View File
@@ -0,0 +1,124 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
shadowsocks-libev is a lightweight SOCKS5 proxy written in pure C. Version 3.3.6, licensed under GPLv3.
## Build Commands
### CMake (sole build system)
```bash
git submodule update --init --recursive
mkdir -p build && cd build
cmake ..
make
sudo make install
```
On macOS, CMake should auto-detect library paths. If needed, specify paths:
```bash
cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/mbedtls;/usr/local/opt/libsodium"
```
CMake outputs binaries to `build/bin/` (static) and `build/shared/bin/` (shared).
### Build Dependencies
- cmake (>= 3.2), a C compiler (gcc or clang), pkg-config
- libmbedtls, libsodium (>= 1.0.4), libpcre3, libev, libc-ares
- asciidoc + xmlto (documentation only)
### CMake Options
- `-DWITH_EMBEDDED_SRC=OFF`: use system libcork/libipset/libbloom instead of bundled submodules
- `-DWITH_DOC_MAN=OFF`: skip man page generation
- `-DENABLE_CONNMARKTOS=ON`: Linux netfilter conntrack QoS support
- `-DENABLE_NFTABLES=ON`: nftables firewall integration
- `-DDISABLE_SSP=ON`: disable stack protector
- `-DBUILD_TESTING=OFF`: disable unit tests
## Testing
### Unit Tests (CTest)
```bash
cd build
ctest --output-on-failure
```
10 unit test modules cover: base64, buffer, crypto, json, jconf, cache, ppbloom, rule, netutils, utils.
### Integration Tests
Integration tests use Python and require `curl` and `dig` to be available:
```bash
bash tests/test.sh
```
The test harness (`tests/test.py`) starts ss-server, ss-local, and ss-tunnel locally, then runs curl through the SOCKS5 proxy and dig through the tunnel. Each test config in `tests/*.json` exercises a different cipher.
Run a single cipher test:
```bash
python tests/test.py --bin build/bin/ -c tests/aes-gcm.json
```
## Code Formatting
Uses **uncrustify** with the config at `.uncrustify.cfg`. Key settings: 4-space indent, no tabs, 120-column width, K&R brace style (braces on same line).
## Architecture
### Binaries (all in `src/`)
Each binary is compiled with a module define that controls conditional compilation:
| Binary | Define | Purpose |
|---|---|---|
| `ss-local` | `MODULE_LOCAL` | SOCKS5 client proxy |
| `ss-server` | `MODULE_REMOTE` | Server-side proxy |
| `ss-tunnel` | `MODULE_TUNNEL` | Port forwarding tunnel (implies `MODULE_LOCAL`) |
| `ss-redir` | `MODULE_REDIR` | Transparent proxy via iptables (Linux only, implies `MODULE_LOCAL`) |
| `ss-manager` | `MODULE_MANAGER` | Multi-server manager daemon |
A shared library `libshadowsocks-libev` is also built from the ss-local sources with `-DLIB_ONLY`. Its public API is in `src/shadowsocks.h`.
### Source Organization (`src/`)
**Shared by all binaries:**
- `utils.c` - logging, system utilities
- `jconf.c` / `json.c` - JSON config file parsing
- `netutils.c` - network address utilities
- `cache.c` - hash-based LRU connection cache
- `udprelay.c` - UDP relay implementation (shared, but uses `#ifdef MODULE_*` for per-binary behavior)
**Crypto layer** (two parallel implementations behind a common `crypto_t` interface):
- `crypto.c` / `crypto.h` - crypto initialization, key derivation (HKDF), buffer management. Defines `crypto_t` with function pointers for encrypt/decrypt.
- `stream.c` - stream cipher implementation (CFB mode via mbedTLS)
- `aead.c` - AEAD cipher implementation (AES-GCM via mbedTLS, ChaCha20-Poly1305 via libsodium)
- `ppbloom.c` - ping-pong bloom filter for nonce replay detection
**ACL (Access Control Lists):**
- `acl.c` / `rule.c` - IP/domain-based routing rules using libipset
**Plugin support:**
- `plugin.c` - SIP003 plugin subprocess management
### Bundled Submodules
Three git submodules in the repo root (can be replaced with system libs via `-DWITH_EMBEDDED_SRC=OFF`):
- `libcork/` - data structures (dllist, hash-table, buffers)
- `libipset/` - IP set operations for ACL
- `libbloom/` - bloom filter implementation
### Event Loop
All binaries use **libev** for async I/O. The connection lifecycle follows stages defined in `src/common.h`: `STAGE_INIT` -> `STAGE_HANDSHAKE` -> `STAGE_RESOLVE` -> `STAGE_STREAM` -> `STAGE_STOP`. Each binary defines its own `listen_ctx_t`, `server_t`, and `remote_t` structs (note: "server" in `local.h` means the local-side connection, "remote" means the ss-server side).
### Compiler Flags
Default flags from `CMakeLists.txt`: `-g -O2 -Wall -Werror -Wno-deprecated-declarations -fno-strict-aliasing -std=gnu99 -D_GNU_SOURCE`
The `-Werror` flag means all warnings are errors - new code must compile warning-free.
+2 -2
View File
@@ -1,8 +1,8 @@
cmake_minimum_required(VERSION 3.5)
set(PROJECT_NAME shadowsocks-libev)
set(RELEASE_DATE 2020-09-15)
set(PROJECT_VERSION "3.3.5")
set(RELEASE_DATE 2026-02-09)
set(PROJECT_VERSION "3.3.6")
set(PROJECT_DESC "a lightweight secured socks5 proxy")
set(PROJECT_URL "https://shadowsocks.org")
set(PROJECT_ISSUES_URL "https://github.com/shadowsocks/shadowsocks-libev")
+1 -1
View File
@@ -11,7 +11,7 @@ It is a port of [Shadowsocks](https://github.com/shadowsocks/shadowsocks)
created by [@clowwindy](https://github.com/clowwindy), and maintained by
[@madeye](https://github.com/madeye) and [@linusyang](https://github.com/linusyang).
Current version: 3.3.5 | [Changelog](debian/changelog)
Current version: 3.3.6 | [Changelog](debian/changelog)
## Features
+1 -1
View File
@@ -11,7 +11,7 @@ proxy para dispositivos embutidos e caixas de baixo custo.
criado por [@clowwindy](https://github.com/clowwindy) e mantido por
[@madeye](https://github.com/madeye) e [@linusyang](https://github.com/linusyang).
Versão atual: 3.3.5 | [Changelog](debian/changelog)
Versão atual: 3.3.6 | [Changelog](debian/changelog)
## Características
+7
View File
@@ -1,3 +1,10 @@
shadowsocks-libev (3.3.6-1) unstable; urgency=medium
* Reduce memory copies in crypto encrypt/decrypt paths.
* Fix unbounded buffer growth in aead_decrypt chunk reassembly.
-- Max Lv <max.c.lv@gmail.com> Sun, 09 Feb 2026 08:00:00 +0800
shadowsocks-libev (3.3.5-1) unstable; urgency=medium
* Remove the SNI proxy function.
+1 -1
View File
@@ -1,6 +1,6 @@
base: core18
name: shadowsocks-libev
version: 3.3.5-1
version: 3.3.6-1
summary: libev port of shadowsocks
description: |
Shadowsocks-libev is a lightweight and secure SOCKS5 proxy for embedded
+40 -35
View File
@@ -439,8 +439,7 @@ aead_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
assert(ciphertext->len == clen);
brealloc(plaintext, salt_len + ciphertext->len, capacity);
memcpy(plaintext->data, ciphertext->data, salt_len + ciphertext->len);
bswap_data(plaintext, ciphertext);
plaintext->len = salt_len + ciphertext->len;
return CRYPTO_OK;
@@ -490,8 +489,7 @@ aead_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
ppbloom_add((void *)salt, salt_len);
brealloc(ciphertext, plaintext->len, capacity);
memcpy(ciphertext->data, plaintext->data, plaintext->len);
bswap_data(ciphertext, plaintext);
ciphertext->len = plaintext->len;
return CRYPTO_OK;
@@ -579,8 +577,7 @@ aead_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
if (err)
return err;
brealloc(plaintext, ciphertext->len, capacity);
memcpy(plaintext->data, ciphertext->data, ciphertext->len);
bswap_data(plaintext, ciphertext);
plaintext->len = ciphertext->len;
return 0;
@@ -634,9 +631,7 @@ aead_chunk_decrypt(cipher_ctx_t *ctx, uint8_t *p, uint8_t *c, uint8_t *n,
int
aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
{
int err = CRYPTO_OK;
static buffer_t tmp = { 0, 0, 0, NULL };
int err = CRYPTO_OK;
cipher_t *cipher = cipher_ctx->cipher;
size_t salt_len = cipher->key_len;
@@ -647,20 +642,32 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
balloc(cipher_ctx->chunk, capacity);
}
brealloc(cipher_ctx->chunk,
cipher_ctx->chunk->len + ciphertext->len, capacity);
memcpy(cipher_ctx->chunk->data + cipher_ctx->chunk->len,
ciphertext->data, ciphertext->len);
cipher_ctx->chunk->len += ciphertext->len;
buffer_t *chunk = cipher_ctx->chunk;
brealloc(&tmp, cipher_ctx->chunk->len, capacity);
buffer_t *plaintext = &tmp;
if (chunk->len == 0) {
bswap_data(chunk, ciphertext);
chunk->len = ciphertext->len;
chunk->idx = 0;
ciphertext->len = 0;
} else {
if (chunk->idx > 0) {
memmove(chunk->data, chunk->data + chunk->idx, chunk->len);
chunk->idx = 0;
}
brealloc(chunk, chunk->len + ciphertext->len, capacity);
memcpy(chunk->data + chunk->len,
ciphertext->data, ciphertext->len);
chunk->len += ciphertext->len;
}
// Write plaintext directly into the (now-free) ciphertext buffer.
brealloc(ciphertext, chunk->len, capacity);
if (!cipher_ctx->init) {
if (cipher_ctx->chunk->len <= salt_len)
if (chunk->len <= salt_len)
return CRYPTO_NEED_MORE;
memcpy(cipher_ctx->salt, cipher_ctx->chunk->data, salt_len);
memcpy(cipher_ctx->salt, chunk->data + chunk->idx, salt_len);
if (ppbloom_check((void *)cipher_ctx->salt, salt_len) == 1) {
LOGE("crypto: AEAD: repeat salt detected");
@@ -669,38 +676,40 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
aead_cipher_ctx_set_key(cipher_ctx, 0);
memmove(cipher_ctx->chunk->data, cipher_ctx->chunk->data + salt_len,
cipher_ctx->chunk->len - salt_len);
cipher_ctx->chunk->len -= salt_len;
chunk->idx += salt_len;
chunk->len -= salt_len;
cipher_ctx->init = 1;
}
size_t plen = 0;
size_t cidx = 0;
while (cipher_ctx->chunk->len > 0) {
size_t chunk_clen = cipher_ctx->chunk->len;
while (chunk->len > 0) {
size_t chunk_clen = chunk->len;
size_t chunk_plen = 0;
err = aead_chunk_decrypt(cipher_ctx,
(uint8_t *)plaintext->data + plen,
(uint8_t *)cipher_ctx->chunk->data + cidx,
(uint8_t *)ciphertext->data + plen,
(uint8_t *)chunk->data + chunk->idx + cidx,
cipher_ctx->nonce, &chunk_plen, &chunk_clen);
if (err == CRYPTO_ERROR) {
return err;
} else if (err == CRYPTO_NEED_MORE) {
if (plen == 0)
return err;
else{
memmove((uint8_t *)cipher_ctx->chunk->data,
(uint8_t *)cipher_ctx->chunk->data + cidx, chunk_clen);
else {
chunk->idx += cidx;
break;
}
}
cipher_ctx->chunk->len = chunk_clen;
chunk->len = chunk_clen;
cidx += cipher_ctx->cipher->tag_len * 2 + CHUNK_SIZE_LEN + chunk_plen;
plen += chunk_plen;
plen += chunk_plen;
}
plaintext->len = plen;
if (chunk->len == 0)
chunk->idx = 0;
ciphertext->len = plen;
// Add the salt to bloom filter
if (cipher_ctx->init == 1) {
@@ -712,10 +721,6 @@ aead_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
cipher_ctx->init = 2;
}
brealloc(ciphertext, plaintext->len, capacity);
memcpy(ciphertext->data, plaintext->data, plaintext->len);
ciphertext->len = plaintext->len;
return CRYPTO_OK;
}
+12
View File
@@ -131,6 +131,18 @@ int balloc(buffer_t *, size_t);
int brealloc(buffer_t *, size_t, size_t);
int bprepend(buffer_t *, buffer_t *, size_t);
void bfree(buffer_t *);
static inline void
bswap_data(buffer_t *a, buffer_t *b)
{
char *tmp_data = a->data;
size_t tmp_capacity = a->capacity;
a->data = b->data;
a->capacity = b->capacity;
b->data = tmp_data;
b->capacity = tmp_capacity;
}
int rand_bytes(void *, int);
crypto_t *crypto_init(const char *, const char *, const char *);
+40 -8
View File
@@ -344,8 +344,7 @@ stream_encrypt_all(buffer_t *plaintext, cipher_t *cipher, size_t capacity)
dump("NONCE", ciphertext->data, nonce_len);
#endif
brealloc(plaintext, nonce_len + ciphertext->len, capacity);
memcpy(plaintext->data, ciphertext->data, nonce_len + ciphertext->len);
bswap_data(plaintext, ciphertext);
plaintext->len = nonce_len + ciphertext->len;
return CRYPTO_OK;
@@ -359,6 +358,19 @@ stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
cipher_t *cipher = cipher_ctx->cipher;
// In-place fast path for non-Salsa20 ciphers after init.
// mbedtls_cipher_update supports output == input for CFB/CTR stream modes.
if (cipher_ctx->init && cipher->method < SALSA20) {
size_t out_len = plaintext->len;
int err = cipher_ctx_update(cipher_ctx,
(uint8_t *)plaintext->data, &out_len,
(const uint8_t *)plaintext->data, plaintext->len);
if (err)
return CRYPTO_ERROR;
plaintext->len = out_len;
return CRYPTO_OK;
}
static buffer_t tmp = { 0, 0, 0, NULL };
int err = CRYPTO_OK;
@@ -416,8 +428,7 @@ stream_encrypt(buffer_t *plaintext, cipher_ctx_t *cipher_ctx, size_t capacity)
dump("CIPHER", ciphertext->data + nonce_len, ciphertext->len);
#endif
brealloc(plaintext, nonce_len + ciphertext->len, capacity);
memcpy(plaintext->data, ciphertext->data, nonce_len + ciphertext->len);
bswap_data(plaintext, ciphertext);
plaintext->len = nonce_len + ciphertext->len;
return CRYPTO_OK;
@@ -475,8 +486,7 @@ stream_decrypt_all(buffer_t *ciphertext, cipher_t *cipher, size_t capacity)
ppbloom_add((void *)nonce, nonce_len);
brealloc(ciphertext, plaintext->len, capacity);
memcpy(ciphertext->data, plaintext->data, plaintext->len);
bswap_data(ciphertext, plaintext);
ciphertext->len = plaintext->len;
return CRYPTO_OK;
@@ -490,6 +500,29 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
cipher_t *cipher = cipher_ctx->cipher;
// In-place fast path for non-Salsa20 ciphers after init.
// mbedtls_cipher_update supports output == input for CFB/CTR stream modes.
if (cipher_ctx->init && cipher->method < SALSA20) {
if (ciphertext->len <= 0)
return CRYPTO_NEED_MORE;
size_t out_len = ciphertext->len;
int err = cipher_ctx_update(cipher_ctx,
(uint8_t *)ciphertext->data, &out_len,
(const uint8_t *)ciphertext->data, ciphertext->len);
if (err)
return CRYPTO_ERROR;
ciphertext->len = out_len;
if (cipher_ctx->init == 1 && cipher->method >= RC4_MD5) {
if (ppbloom_check((void *)cipher_ctx->nonce, cipher->nonce_len) == 1) {
LOGE("crypto: stream: repeat IV detected");
return CRYPTO_ERROR;
}
ppbloom_add((void *)cipher_ctx->nonce, cipher->nonce_len);
cipher_ctx->init = 2;
}
return CRYPTO_OK;
}
static buffer_t tmp = { 0, 0, 0, NULL };
int err = CRYPTO_OK;
@@ -585,8 +618,7 @@ stream_decrypt(buffer_t *ciphertext, cipher_ctx_t *cipher_ctx, size_t capacity)
}
}
brealloc(ciphertext, plaintext->len, capacity);
memcpy(ciphertext->data, plaintext->data, plaintext->len);
bswap_data(ciphertext, plaintext);
ciphertext->len = plaintext->len;
return CRYPTO_OK;
+6 -6
View File
@@ -1026,17 +1026,17 @@ These Ciphers require `"password"` to be a Base64 string of key that have **exac
- For local servers (`sslocal`, `ssredir`, ...)
- Modes:
- `[bypass_all]` - ACL runs in `WhiteList` mode. Bypasses all addresses except those matched any rules.
- `[proxy_all]` - ACL runs in `BlackList` mode. Proxies all addresses except those matched any rules. (default)
- `[bypass_all]` - ACL runs in `WhiteList` mode. Bypasses all addresses except those matching any rules.
- `[proxy_all]` - ACL runs in `BlackList` mode. Proxies all addresses except those matching any rules. (default)
- Rules:
- `[bypass_list]` - Rules for connecting directly
- `[proxy_list]` - Rules for connecting through proxies
- For remote servers (`ssserver`)
- Modes:
- `[reject_all]` - ACL runs in `WhiteList` mode. Rejects all clients except those matched any rules.
- `[accept_all]` - ACL runs in `BlackList` mode. Accepts all clients except those matched any rules. (default)
- `[outbound_block_all]` - Outbound ACL runs in `WhiteList` mode. Blockes all outbound addresses except those matched any rules.
- `[outbound_allow_all]` - Outbound ACL runs in `BlackList` mode. Allows all outbound addresses except those matched any rules. (default)
- `[reject_all]` - ACL runs in `WhiteList` mode. Rejects all clients except those matching any rules.
- `[accept_all]` - ACL runs in `BlackList` mode. Accepts all clients except those matching any rules. (default)
- `[outbound_block_all]` - Outbound ACL runs in `WhiteList` mode. Blocks all outbound addresses except those matching any rules.
- `[outbound_allow_all]` - Outbound ACL runs in `BlackList` mode. Allows all outbound addresses except those matching any rules. (default)
- Rules:
- `[white_list]` - Rules for accepted clients
- `[black_list]` - Rules for rejected clients
+3 -3
View File
@@ -21,7 +21,7 @@ use windows_service::{
const SERVICE_NAME: &str = "ssservice";
const SERVICE_EXIT_CODE_ARGUMENT_ERROR: u32 = 100;
const SERVICE_EXIT_CODE_EXITED_UNEXPECTLY: u32 = 101;
const SERVICE_EXIT_CODE_EXITED_UNEXPECTEDLY: u32 = 101;
const SERVICE_EXIT_CODE_CREATE_FAILED: u32 = 102;
#[inline]
@@ -84,7 +84,7 @@ where
break true;
}
exit_code = main_fut => {
info!("service exited unexpectly with code: {:?}", exit_code);
info!("service exited unexpectedly with code: {:?}", exit_code);
break false;
}
}
@@ -98,7 +98,7 @@ where
if exited_by_ctrl {
ServiceExitCode::Win32(0)
} else {
ServiceExitCode::ServiceSpecific(SERVICE_EXIT_CODE_EXITED_UNEXPECTLY)
ServiceExitCode::ServiceSpecific(SERVICE_EXIT_CODE_EXITED_UNEXPECTEDLY)
},
Duration::default(),
)?;
@@ -619,8 +619,8 @@ impl AccessControl {
// If no domain name rules matched,
// we need to resolve the hostname to IP addresses
// If mode is BlackList, host is allowed by default. If any of its' resolved IPs in outboud_block, then it is blocked.
// If mode is WhiteList, host is blocked by default. If any of its' resolved IPs in outbound_allow, then it is allowed.
// If mode is BlackList, host is allowed by default. If any of its resolved IPs is in outbound_block, then it is blocked.
// If mode is WhiteList, host is blocked by default. If any of its resolved IPs is in outbound_allow, then it is allowed.
let (check_rule, block_if_matched) = match self.outbound_mode {
Mode::BlackList => (&self.outbound_block, true),
Mode::WhiteList => (&self.outbound_allow, false),
@@ -883,7 +883,7 @@ impl DnsClient {
.map_err(From::from)
}
Mode::TcpAndUdp => {
// Query TCP & UDP simutaneously
// Query TCP & UDP simultaneously
let message2 = message.clone();
let tcp_fut = async {
@@ -952,7 +952,7 @@ impl DnsClient {
self.client_cache
.lookup_local(ns, message.clone(), self.context.connect_opts_ref(), true);
let tcp_query = async move {
// Send TCP query after 500ms, because UDP will always return faster than TCP, there is no need to send queries simutaneously
// Send TCP query after 500ms, because UDP will always return faster than TCP, there is no need to send queries simultaneously
time::sleep(Duration::from_millis(500)).await;
self.client_cache
@@ -77,7 +77,7 @@ impl Debug for ServerScore {
}
}
/// Identifer for a server
/// Identifier for a server
#[derive(Debug)]
pub struct ServerIdent {
tcp_score: ServerScore,
@@ -90,7 +90,7 @@ impl ServerStat {
// Normalize stdev
// let nstdev = self.data.latency_stdev / self.max_latency_stdev;
// Mormalize mad
// Normalize mad
let nmad = self.data.latency_mad as f64 / self.max_server_rtt as f64;
const SCORE_RTT_WEIGHT: f64 = 1.0;
@@ -128,7 +128,7 @@ impl OnlineConfigService {
}
};
trace!("sever-loader task fetch response: {:?}", rsp);
trace!("server-loader task fetch response: {:?}", rsp);
let fetch_time = Instant::now();
@@ -260,7 +260,7 @@ impl RedirUdpServer {
}
peer_addr_opt = keepalive_rx.recv() => {
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
manager.keep_alive(&peer_addr).await;
}
@@ -167,7 +167,7 @@ impl Socks5UdpServer {
}
peer_addr_opt = keepalive_rx.recv() => {
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
manager.keep_alive(&peer_addr).await;
}
@@ -133,7 +133,7 @@ async fn handle_tcp_client(
let server = balancer.best_tcp_server();
let svr_cfg = server.server_config();
trace!(
"establishing tcp tunnel {} <-> {} through sever {} (outbound: {})",
"establishing tcp tunnel {} <-> {} through server {} (outbound: {})",
peer_addr,
forward_addr,
svr_cfg.tcp_external_addr(),
@@ -134,7 +134,7 @@ impl TunnelUdpServer {
}
peer_addr_opt = keepalive_rx.recv() => {
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectly");
let peer_addr = peer_addr_opt.expect("keep-alive channel closed unexpectedly");
manager.keep_alive(&peer_addr).await;
}
@@ -27,7 +27,7 @@ where
{
if shadow.is_proxied() {
debug!(
"established tcp tunnel {} <-> {} through sever {} (outbound: {})",
"established tcp tunnel {} <-> {} through server {} (outbound: {})",
peer_addr,
target_addr,
svr_cfg.tcp_external_addr(),
@@ -15,7 +15,6 @@ use shadowsocks::{
net::{AcceptOpts, TcpStream as OutboundTcpStream},
relay::tcprelay::{ProxyServerStream, utils::copy_encrypted_bidirectional},
};
use socket2::SockRef;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream as TokioTcpStream,
@@ -166,7 +165,7 @@ impl TcpServerClient {
// tokio's TcpStream.set_linger was marked as deprecated.
// But we set linger(0), which won't block the thread when close() the socket.
let _ = SockRef::from(&stream).set_linger(Some(Duration::ZERO));
let _ = socket2::SockRef::from(&stream).set_linger(Some(Duration::ZERO));
return Ok(());
}
@@ -516,7 +516,7 @@ where
#[cfg(feature = "stream-cipher")]
CipherKind::SS_TABLE => {
// TABLE cipher doesn't need key derivation.
// Reference implemenation: shadowsocks-libev, shadowsocks (Python)
// Reference implementation: shadowsocks-libev, shadowsocks (Python)
let enc_key = password.clone().into_bytes().into_boxed_slice();
return Ok((password, enc_key, Vec::new()));
}
@@ -122,7 +122,7 @@ pub mod android {
use sealed::sealed;
use std::{fmt, io, os::unix::io::RawFd};
/// Android VPN socket protect implemetation
/// Android VPN socket protect implementation
#[sealed]
pub trait SocketProtect {
/// Protects the socket file descriptor by calling `VpnService.protect(fd)`
+7 -7
View File
@@ -440,11 +440,11 @@ shadowsocks-rust (1.15.0) unstable; urgency=medium
- shadowsocks/shadowsocks-org#204 Remove `security-iv-printable-prefix` experimental feature
- Upgrade [clap](https://crates.io/crates/clap) to v4.0.
- UDP association channel size shrinked to 1024, which could save a lot of memory for each associations
- UDP association channel size shrunk to 1024, which could save a lot of memory for each associations
## Enhancements
- #855 Properly handle IPv4-mapped-IPv6 addresses in UDP assocations
- #855 Properly handle IPv4-mapped-IPv6 addresses in UDP associations
- `ssserver` always convert IPv4-mapped-IPv6 addresses to IPv4 in UDP respond target addresses
- `sslocal`
- `tun`, `socks5` always convert IPv4-mapped-IPv6 addresses to IPv4
@@ -476,11 +476,11 @@ shadowsocks-rust (1.14.0) unstable; urgency=medium
## Features
- Optimization: UDP associations managment removes unnecessary locks
- Optimization: UDP associations management removes unnecessary locks
## Breaking Changes
- `shadowsocks_service::local::net::udp::UdpAssociationManager::new` returns clean-up interval and keep-alive receiver. It is not recommended to use this directly in your Project, because the API may change occationally.
- `shadowsocks_service::local::net::udp::UdpAssociationManager::new` returns clean-up interval and keep-alive receiver. It is not recommended to use this directly in your Project, because the API may change occasionally.
shadowsocks-rust (1.13.5) unstable; urgency=medium
@@ -538,7 +538,7 @@ shadowsocks-rust (1.13.0) unstable; urgency=medium
- When server start, the "check best" strategy starts after it receives enough data for estimating all servers' scores
- #744 Refactored `local-tun`, using `smoltcp` as a user-space network stack
## Miscelleneous
## Miscellaneous
- Upgrade [`clap`](https://crates.io/crates/clap) (the command-line argument handling library) to v3.0
- #739 Support K8S deployment
@@ -569,7 +569,7 @@ shadowsocks-rust (1.12.4) unstable; urgency=medium
- #694 UDP binds to dual-stack address (`::`) will detect `0.0.0.0` already in use automatically
- Transparent Proxy (redir) local client will always set `IPV6_V6ONLY` for listeners that are listening on `::`
## Miscelleneous
## Miscellaneous
- Deprecating `SS_LOG_VERBOSE_LEVEL` and `SS_LOG_WITHOUT_TIME` (introduced in [v1.12.3](https://github.com/shadowsocks/shadowsocks-rust/releases/tag/v1.12.3)) in flavor of configuring in the configuration file
@@ -855,7 +855,7 @@ shadowsocks-rust (1.9.0) unstable; urgency=medium
* Support setting SO_SNDBUF and SO_RCVBUF for TCP sockets
* Support [SIP008](https://github.com/shadowsocks/shadowsocks-org/issues/89 "Online config") extend server fields server, server_port, remarks
* Local DNS Relay
* Support sending TCP and UDP queries simutaneously
* Support sending TCP and UDP queries simultaneously
* Support connection reusability
* Remove mostly TCP timeout setting for tunnels, connections will only be killed if clients or servers close
* Auto-reload DNS resolver configuration from /etc/resolv.conf on *NIX platforms.
+5 -9
View File
@@ -206,7 +206,7 @@ update_apple_version:
update_macos_version:
MACOS_PROJECT_VERSION=$(shell go run -v ./cmd/internal/app_store_connect next_macos_project_version) go run ./cmd/internal/update_apple_version
release_apple: lib_ios update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple: lib_apple update_apple_version release_ios release_macos release_tvos release_macos_standalone
release_apple_beta: update_apple_version release_ios release_macos release_tvos
@@ -234,18 +234,14 @@ test_stdio:
lib_android:
go run ./cmd/internal/build_libbox -target android
lib_android_debug:
go run ./cmd/internal/build_libbox -target android -debug
lib_apple:
go run ./cmd/internal/build_libbox -target apple
lib_ios:
go run ./cmd/internal/build_libbox -target apple -platform ios -debug
lib_android_new:
go run ./cmd/internal/build_libbox_newffi -target android
lib:
go run ./cmd/internal/build_libbox -target android
go run ./cmd/internal/build_libbox -target ios
lib_apple_new:
go run ./cmd/internal/build_libbox_newffi -target apple
lib_install:
go install -v github.com/sagernet/gomobile/cmd/gomobile@v0.1.11
@@ -194,13 +194,13 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.13.0")
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
implementation("androidx.navigation:navigation-fragment-ktx:2.9.6")
implementation("androidx.navigation:navigation-ui-ktx:2.9.6")
implementation("androidx.navigation:navigation-fragment-ktx:2.9.7")
implementation("androidx.navigation:navigation-ui-ktx:2.9.7")
implementation("com.google.zxing:core:3.5.4")
implementation("androidx.coordinatorlayout:coordinatorlayout:1.3.0")
implementation("androidx.preference:preference-ktx:1.2.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
implementation("com.blacksquircle.ui:editorkit:2.2.0")
implementation("com.blacksquircle.ui:language-json:2.2.0")
implementation("com.android.tools.smali:smali-dexlib2:3.0.9") {
@@ -113,14 +113,19 @@ fun ProfileOverrideScreen(navController: NavController) {
val isShizukuBinderReady by PackageQueryManager.shizukuBinderReady.collectAsState()
val isShizukuPermissionGranted by PackageQueryManager.shizukuPermissionGranted.collectAsState()
val isShizukuAvailable = isShizukuBinderReady && isShizukuPermissionGranted
var isShizukuStateInitialized by remember(showModeSelector) { mutableStateOf(!showModeSelector) }
DisposableEffect(showModeSelector) {
if (showModeSelector) {
isShizukuStateInitialized = false
PackageQueryManager.registerListeners()
PackageQueryManager.refreshShizukuState()
isShizukuStateInitialized = true
}
onDispose {
if (showModeSelector) {
PackageQueryManager.unregisterListeners()
isShizukuStateInitialized = false
}
}
}
@@ -140,8 +145,14 @@ fun ProfileOverrideScreen(navController: NavController) {
}
// Auto-disable per-app proxy if Shizuku authorization is revoked (only when using Shizuku mode)
LaunchedEffect(isShizukuAvailable, useRootMode) {
if (showModeSelector && !useRootMode && !isShizukuAvailable && perAppProxyEnabled) {
LaunchedEffect(isShizukuAvailable, useRootMode, isShizukuStateInitialized, perAppProxyEnabled, showModeSelector) {
if (
showModeSelector &&
!useRootMode &&
isShizukuStateInitialized &&
perAppProxyEnabled &&
!PackageQueryManager.isShizukuAvailable()
) {
perAppProxyEnabled = false
withContext(Dispatchers.IO) {
Settings.perAppProxyEnabled = false
@@ -631,7 +642,11 @@ fun ProfileOverrideScreen(navController: NavController) {
scope.launch(Dispatchers.IO) {
Settings.perAppProxyPackageQueryMode = Settings.PACKAGE_QUERY_MODE_SHIZUKU
}
if (perAppProxyEnabled && !isShizukuAvailable) {
if (
perAppProxyEnabled &&
isShizukuStateInitialized &&
!PackageQueryManager.isShizukuAvailable()
) {
perAppProxyEnabled = false
scope.launch(Dispatchers.IO) {
Settings.perAppProxyEnabled = false
@@ -0,0 +1,436 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<!-- App -->
<string name="app_name" translatable="false">sing-box</string>
<string name="app_version_title">نسخه برنامه</string>
<!-- Common Actions -->
<string name="ok">تأیید</string>
<string name="cancel">لغو</string>
<string name="save">ذخیره</string>
<string name="discard">چشم‌پوشی</string>
<string name="edit">ویرایش</string>
<string name="close">بستن</string>
<string name="dismiss">بستن</string>
<string name="stop">توقف</string>
<string name="reset">بازنشانی</string>
<string name="destroy">حذف</string>
<string name="clear">پاک کردن</string>
<string name="browse">مرور</string>
<string name="search">جستجو</string>
<string name="action">اقدام</string>
<string name="action_start">شروع</string>
<string name="action_deselect">لغو انتخاب</string>
<string name="expand">باز کردن</string>
<string name="collapse">جمع کردن</string>
<string name="expand_all">باز کردن همه</string>
<string name="collapse_all">جمع کردن همه</string>
<string name="previous">قبلی</string>
<string name="next">بعدی</string>
<string name="update">به‌روزرسانی</string>
<string name="read_more">بیشتر بخوانید</string>
<string name="no_thanks">نه، ممنون</string>
<string name="options">گزینه‌ها</string>
<string name="more_options">گزینه‌های بیشتر</string>
<!-- Common States -->
<string name="enabled">فعال</string>
<string name="disabled">غیرفعال</string>
<string name="loading">در حال بارگذاری…</string>
<string name="calculating">در حال محاسبه...</string>
<string name="auto">خودکار</string>
<string name="success">موفق</string>
<string name="default_text">پیش‌فرض</string>
<!-- Navigation Titles -->
<string name="title_dashboard">داشبورد</string>
<string name="title_configuration">پروفایل‌ها</string>
<string name="title_log">گزارش‌ها</string>
<string name="title_settings">تنظیمات</string>
<string name="title_app_settings">برنامه</string>
<string name="title_new_profile">پروفایل جدید</string>
<string name="title_edit_profile">ویرایش پروفایل</string>
<string name="title_edit_configuration">ویرایش پیکربندی</string>
<string name="title_groups">گروه‌ها</string>
<string name="title_debug">اشکال‌زدایی</string>
<string name="title_connections">اتصال‌ها</string>
<string name="title_others">سایر</string>
<string name="title_scan_result">نتیجه اسکن</string>
<!-- Status -->
<string name="status_default">سرویس شروع نشده است</string>
<string name="status_starting">در حال شروع</string>
<string name="status_stopping">در حال توقف</string>
<string name="status_started">شروع شده</string>
<!-- Dashboard -->
<string name="dashboard_items">آیتم‌های داشبورد</string>
<string name="memory">حافظه</string>
<string name="goroutines">گوروتین‌ها</string>
<string name="upload">آپلود</string>
<string name="download">دانلود</string>
<string name="connections_in">ورودی</string>
<string name="connections_out">خروجی</string>
<string name="clash_mode">حالت Clash</string>
<string name="mode">حالت</string>
<string name="system_proxy">پراکسی سیستم</string>
<string name="url_test">تست</string>
<string name="drag_handle_to_reorder">برای جابه‌جایی آیتم‌ها، دستگیره را بکشید</string>
<string name="drag_to_reorder">برای جابه‌جایی بکشید</string>
<string name="reset_order">بازنشانی ترتیب</string>
<!-- Connections -->
<string name="empty_connections">هیچ اتصالی وجود ندارد</string>
<string name="search_connections">جستجوی اتصال‌ها…</string>
<string name="close_connections_confirm">همه اتصال‌ها بسته شوند؟</string>
<string name="connection_state_all">همه</string>
<string name="connection_state_active">فعال</string>
<string name="connection_state_closed">بسته</string>
<string name="connection_sort_date">تاریخ</string>
<string name="connection_sort_traffic">ترافیک</string>
<string name="connection_sort_traffic_total">ترافیک کل</string>
<string name="connection_close">بستن</string>
<string name="connection_close_all">بستن همه</string>
<string name="connection_state">وضعیت</string>
<string name="connection_details">جزئیات اتصال</string>
<string name="connection_section_basic">اطلاعات پایه</string>
<string name="connection_section_metadata">فراداده</string>
<string name="connection_section_process">اطلاعات فرایند</string>
<string name="connection_created_at">زمان ایجاد</string>
<string name="connection_closed_at">زمان بسته شدن</string>
<string name="connection_duration">مدت‌زمان</string>
<string name="connection_uplink">آپ‌لینک</string>
<string name="connection_downlink">دان‌لینک</string>
<string name="connection_inbound">ورودی</string>
<string name="connection_inbound_type">نوع ورودی</string>
<string name="connection_outbound">خروجی</string>
<string name="connection_outbound_type">نوع خروجی</string>
<string name="connection_ip_version">نسخه IP</string>
<string name="connection_network">شبکه</string>
<string name="connection_source">مبدأ</string>
<string name="connection_destination">مقصد</string>
<string name="connection_domain">دامنه</string>
<string name="connection_protocol">پروتکل</string>
<string name="connection_user">کاربر</string>
<string name="connection_from_outbound">از خروجی</string>
<string name="connection_match_rule">قانون تطبیق</string>
<string name="connection_chain">زنجیره</string>
<string name="connection_process_id">شناسه فرایند</string>
<string name="connection_user_id">شناسه کاربر</string>
<string name="connection_user_name">نام کاربر</string>
<string name="connection_process_path">مسیر فرایند</string>
<string name="connection_package_name">نام بسته</string>
<!-- Profiles -->
<string name="no_profiles">هیچ پروفایلی تنظیم نشده است</string>
<string name="add_profile">افزودن پروفایل</string>
<string name="update_profile">به‌روزرسانی پروفایل</string>
<string name="import_remote_profile">وارد کردن پروفایل راه دور</string>
<string name="import_remote_profile_message">آیا از وارد کردن پروفایل راه دور %1$s مطمئن هستید؟ برای دانلود پیکربندی به %2$s متصل می‌شوید.</string>
<string name="import_from_file_description">وارد کردن پیکربندی از فایل محلی</string>
<string name="scan_qr_code_description">اسکن QR کد پیکربندی</string>
<string name="create_new_profile_description">ایجاد پروفایل جدید از ابتدا</string>
<string name="icloud_profile_unsupported">پروفایل iCloud در پلتفرم فعلی پشتیبانی نمی‌شود</string>
<string name="profile_name">نام</string>
<string name="profile_type">نوع</string>
<string name="profile_source">منبع</string>
<string name="profile_url" translatable="false">URL</string>
<string name="profile_import_file">وارد کردن فایل</string>
<string name="profile_create">ایجاد</string>
<string name="profile_share_url">اشتراک‌گذاری URL به‌صورت QR کد</string>
<string name="profile_input_required">الزامی</string>
<string name="profile_update">به‌روزرسانی</string>
<string name="profile_auto_update">به‌روزرسانی خودکار</string>
<string name="profile_auto_update_interval">بازه به‌روزرسانی خودکار (دقیقه)</string>
<string name="profile_auto_update_interval_minimum_hint">حداقل مقدار 15 است</string>
<string name="profile_type_local">محلی</string>
<string name="profile_type_remote">راه دور</string>
<string name="profile_type_remote_updated">راه دور • %s</string>
<string name="profile_source_create_new">ایجاد مورد جدید</string>
<string name="profile_source_import">وارد کردن</string>
<string name="profile_add_import_file">وارد کردن از فایل</string>
<string name="profile_add_scan_qr_code">اسکن QR کد</string>
<string name="profile_add_create_manually">ایجاد دستی</string>
<string name="profile_override">بازنویسی پروفایل</string>
<string name="basic_information">اطلاعات پایه</string>
<string name="remote_configuration">پیکربندی راه دور</string>
<string name="last_updated_format">آخرین به‌روزرسانی: %s</string>
<string name="content">محتوا</string>
<string name="json_viewer">نمایشگر JSON</string>
<string name="json_editor">ویرایشگر JSON</string>
<string name="view_configuration">مشاهده پیکربندی</string>
<string name="save_as_file">ذخیره به‌صورت فایل</string>
<string name="share_as_file">اشتراک‌گذاری به‌صورت فایل</string>
<string name="save_content_json">ذخیره محتوای JSON</string>
<string name="share_content_json">اشتراک‌گذاری محتوای JSON</string>
<string name="unsaved_changes">تغییرات ذخیره‌نشده</string>
<string name="unsaved_changes_message">تغییرات ذخیره‌نشده دارید. می‌خواهید از آن‌ها صرف‌نظر کنید؟</string>
<string name="import_profile_confirm_title">وارد کردن پروفایل</string>
<string name="import_profile_confirm_message">وارد کردن پروفایل \"%s\"؟</string>
<string name="import_action">وارد کردن</string>
<!-- Groups -->
<string name="group_selected_title">انتخاب‌شده</string>
<!-- Log -->
<string name="log_level">سطح لاگ</string>
<string name="filter_label">فیلتر: %s</string>
<string name="clear_filter">پاک کردن</string>
<string name="clear_logs">پاک کردن لاگ‌ها</string>
<string name="search_logs_placeholder">جستجوی لاگ‌ها…</string>
<string name="logs_copied_to_clipboard">لاگ‌ها در کلیپ‌بورد کپی شدند</string>
<string name="no_logs_to_copy">لاگی برای کپی وجود ندارد</string>
<string name="no_logs_to_share">لاگی برای اشتراک‌گذاری وجود ندارد</string>
<string name="intent_share_logs">اشتراک‌گذاری لاگ‌ها</string>
<string name="selected_count">%d انتخاب شده</string>
<string name="not_selected">انتخاب نشده</string>
<string name="save_to_clipboard">به کلیپ‌بورد</string>
<string name="save_to_file">به فایل</string>
<!-- Settings -->
<string name="service">سرویس</string>
<string name="core">هسته</string>
<string name="core_version_title">نسخه هسته</string>
<string name="core_data_size">اندازه داده</string>
<string name="about">درباره</string>
<string name="source_code">کد منبع</string>
<string name="sponsor">حامی مالی</string>
<string name="working_directory">پوشه کاری</string>
<string name="disable_deprecated_warnings">غیرفعال‌کردن هشدارهای منسوخ</string>
<string name="ignore_memory_limit">نادیده گرفتن محدودیت حافظه</string>
<string name="ignore_memory_limit_description">محدودیت حافظه روی sing-box اعمال نشود.</string>
<string name="auto_redirect">تغییر مسیر خودکار</string>
<string name="auto_redirect_description">نیازمند دسترسی ROOT</string>
<string name="system_http_proxy">پراکسی HTTP سیستم</string>
<string name="update_settings">تنظیمات به‌روزرسانی</string>
<!-- Per-App Proxy -->
<string name="per_app_proxy">پراکسی هر برنامه</string>
<string name="per_app_proxy_mode">حالت پراکسی</string>
<string name="per_app_proxy_mode_include">شامل</string>
<string name="per_app_proxy_mode_include_description">فقط برنامه‌های انتخاب‌شده از VPN عبور می‌کنند</string>
<string name="per_app_proxy_mode_exclude">استثنا</string>
<string name="per_app_proxy_mode_exclude_description">برنامه‌های انتخاب‌شده از VPN مستثنی می‌شوند</string>
<string name="per_app_proxy_action_copy">کپی</string>
<string name="per_app_proxy_action_copy_package_name">نام بسته</string>
<string name="per_app_proxy_action_copy_uid">UID</string>
<string name="per_app_proxy_sort_mode">مرتب‌سازی بر اساس</string>
<string name="per_app_proxy_sort_mode_name">بر اساس نام</string>
<string name="per_app_proxy_sort_mode_package_name">بر اساس نام بسته</string>
<string name="per_app_proxy_sort_mode_uid">بر اساس UID</string>
<string name="per_app_proxy_sort_mode_install_time">بر اساس زمان نصب</string>
<string name="per_app_proxy_sort_mode_update_time">بر اساس زمان به‌روزرسانی</string>
<string name="per_app_proxy_sort_mode_reverse">معکوس</string>
<string name="per_app_proxy_filter">فیلتر</string>
<string name="per_app_proxy_hide_system_apps">پنهان کردن برنامه‌های سیستمی</string>
<string name="per_app_proxy_hide_offline_apps">پنهان کردن برنامه‌های آفلاین</string>
<string name="per_app_proxy_hide_disabled_apps">پنهان کردن برنامه‌های غیرفعال</string>
<string name="per_app_proxy_select">انتخاب</string>
<string name="per_app_proxy_select_all">انتخاب همه</string>
<string name="per_app_proxy_select_none">لغو انتخاب همه</string>
<string name="per_app_proxy_backup">پشتیبان‌گیری</string>
<string name="per_app_proxy_import">وارد کردن از کلیپ‌بورد</string>
<string name="per_app_proxy_export">خروجی به کلیپ‌بورد</string>
<string name="per_app_proxy_scan">اسکن</string>
<string name="per_app_proxy_scan_china_apps">برنامه‌های چینی</string>
<string name="per_app_proxy_manage">مدیریت</string>
<string name="content_description_app_icon">آیکن برنامه</string>
<string name="per_app_proxy_managed_mode">حالت مدیریت‌شده</string>
<string name="per_app_proxy_managed_mode_description">برنامه‌های چینی را خودکار مستثنی کن</string>
<string name="per_app_proxy_package_query_mode">حالت</string>
<string name="per_app_proxy_shizuku_required">وقتی از Play Store نصب می‌شود، پراکسی هر برنامه به Shizuku نیاز دارد. Google Play اجازه استفاده از مجوز QUERY_ALL_PACKAGES را به ما نمی‌دهد (در حالی که برنامه‌های مشابه دیگر را منع نمی‌کند) و این مجوز برای فهرست کردن برنامه‌ها لازم است.</string>
<string name="per_app_proxy_root_required">وقتی از Play Store نصب می‌شود، پراکسی هر برنامه به ROOT نیاز دارد. Google Play اجازه استفاده از مجوز QUERY_ALL_PACKAGES را به ما نمی‌دهد (در حالی که برنامه‌های مشابه دیگر را منع نمی‌کند) و این مجوز برای فهرست کردن برنامه‌ها لازم است.</string>
<string name="privileged_access_required">برای دریافت فهرست کامل برنامه‌ها به دسترسی Root یا Shizuku نیاز است</string>
<!-- Permissions -->
<string name="background_permission">مجوز پس‌زمینه</string>
<string name="background_permission_description">برای کارکرد صحیح VPN، مجوزهای لازم را اعطا کنید.\n\nاگر از دستگاهی ساخت شرکت‌های چینی استفاده می‌کنید، ممکن است این کارت بعد از اعطای مجوز ناپدید نشود.</string>
<string name="request_background_permission">نادیده گرفتن</string>
<string name="location_permission_title">مجوز موقعیت مکانی</string>
<string name="location_permission_description">پروفایل شما شامل قوانین مسیریابی &lt;strong&gt;&lt;tt&gt;wifi_ssid&lt;/tt&gt; یا &lt;tt&gt;wifi_bssid&lt;/tt&gt;&lt;/strong&gt; است. برای کارکرد آن‌ها، sing-box از مجوز &lt;strong&gt;موقعیت مکانی&lt;/strong&gt; &lt;strong&gt;در پس‌زمینه&lt;/strong&gt; برای دریافت اطلاعات شبکه Wi-Fi متصل استفاده می‌کند. این اطلاعات &lt;strong&gt;فقط برای مسیریابی&lt;/strong&gt; استفاده می‌شوند.</string>
<string name="location_permission_background_description">در Android 10 و بالاتر، مجوز &lt;strong&gt;موقعیت مکانی پس‌زمینه&lt;/strong&gt; لازم است. برای اعطای مجوز، گزینه &lt;strong&gt;اجازه دائمی&lt;/strong&gt; را انتخاب کنید.</string>
<string name="notification_permission_title">مجوز اعلان</string>
<string name="notification_permission_required_description">بدون مجوز اعلان، sing-box نمی‌تواند سرعت لحظه‌ای شبکه را نمایش دهد. لطفا پیش از شروع سرویس، مجوز را بدهید یا اعلان سرعت لحظه‌ای شبکه را غیرفعال کنید.</string>
<string name="root_access_required">دسترسی Root لازم است</string>
<string name="root_access_denied">دسترسی Root رد شد</string>
<!-- Updates -->
<string name="check_update">بررسی به‌روزرسانی</string>
<string name="check_update_automatic">بررسی خودکار به‌روزرسانی</string>
<string name="check_update_prompt_play">می‌خواهید بررسی خودکار به‌روزرسانی از **Play Store** فعال شود؟</string>
<string name="check_update_prompt_github">می‌خواهید بررسی خودکار به‌روزرسانی از **GitHub** فعال شود؟</string>
<string name="update_track">کانال به‌روزرسانی</string>
<string name="update_track_stable">پایدار</string>
<string name="update_track_beta">بتا</string>
<string name="update_track_not_supported">کانال فعلی هنوز از بررسی به‌روزرسانی پشتیبانی نمی‌کند</string>
<string name="view_release">مشاهده انتشار</string>
<string name="downloading">در حال دانلود…</string>
<string name="exporting">در حال خروجی‌گیری…</string>
<string name="no_updates_available">به‌روزرسانی‌ای موجود نیست</string>
<string name="new_version_available">نسخه جدید موجود است: %s</string>
<string name="auto_update">به‌روزرسانی خودکار</string>
<string name="auto_update_description">دانلود و نصب خودکار به‌روزرسانی‌ها در پس‌زمینه</string>
<!-- Silent Install -->
<string name="silent_install">نصب بی‌صدا</string>
<string name="silent_install_description">نصب به‌روزرسانی بدون تعامل</string>
<string name="silent_install_method">روش نصب</string>
<string name="silent_install_verify_failed">%s در دسترس نیست یا دسترسی رد شده است</string>
<string name="install_method_package_installer">PackageInstaller</string>
<string name="install_method_shizuku">Shizuku</string>
<string name="install_method_root">ROOT</string>
<string name="package_installer_not_available">مجوز نصب داده نشده است</string>
<string name="grant_install_permission">اعطای مجوز نصب</string>
<string name="grant_install_permission_description">اجازه نصب برنامه از این منبع</string>
<!-- Shizuku -->
<string name="shizuku_not_available">Shizuku نصب نیست یا در حال اجرا نیست</string>
<string name="shizuku_description">Shizuku به برنامه‌ها اجازه می‌دهد APIهای سیستم را با سطح دسترسی بالاتر مستقیما استفاده کنند</string>
<string name="get_shizuku">دریافت Shizuku</string>
<string name="start_shizuku">شروع Shizuku</string>
<string name="request_shizuku">درخواست Shizuku</string>
<!-- Time -->
<string name="time_just_now">همین الآن</string>
<string name="time_yesterday">دیروز</string>
<string name="time_now">اکنون</string>
<string name="time_yesterday_short">1d</string>
<string name="time_minutes_short">%dm</string>
<string name="time_hours_short">%dh</string>
<string name="time_days_short">%dd</string>
<plurals name="time_minutes_ago">
<item quantity="one">%d دقیقه پیش</item>
<item quantity="other">%d دقیقه پیش</item>
</plurals>
<plurals name="time_hours_ago">
<item quantity="one">%d ساعت پیش</item>
<item quantity="other">%d ساعت پیش</item>
</plurals>
<plurals name="time_days_ago">
<item quantity="one">%d روز پیش</item>
<item quantity="other">%d روز پیش</item>
</plurals>
<!-- Menu -->
<string name="menu_undo">واگرد</string>
<string name="menu_redo">انجام دوباره</string>
<string name="menu_format">قالب‌بندی</string>
<string name="menu_delete">حذف</string>
<string name="menu_share">اشتراک‌گذاری</string>
<!-- Errors -->
<string name="error_title">خطا</string>
<string name="error_missing_vpn_permission">مجوز VPN وجود ندارد</string>
<string name="error_empty_configuration">پیکربندی خالی است</string>
<string name="error_empty_file">فایل خالی است</string>
<string name="error_decode_profile">بازگشایی پروفایل ناموفق بود: %s</string>
<string name="error_invalid_configuration">پیکربندی sing-box نامعتبر است: %s</string>
<string name="error_start_command_server">شروع سرور فرمان</string>
<string name="error_create_service">ایجاد سرویس</string>
<string name="error_start_service">شروع سرویس</string>
<string name="error_deprecated_warning">هشدار منسوخ</string>
<string name="error_deprecated_documentation">مستندات</string>
<string name="file_manager_missing">دستگاه شما فاقد فایل‌گزین استاندارد Android است، لطفا یکی مثل Material Files نصب کنید.</string>
<string name="no_file_manager">مدیر فایل پیدا نشد</string>
<!-- Failed Operations -->
<string name="failed_save_profile">ذخیره پروفایل ناموفق بود: %s</string>
<string name="failed_save_logs">ذخیره لاگ‌ها ناموفق بود: %s</string>
<string name="failed_share_logs">اشتراک‌گذاری لاگ‌ها ناموفق بود: %s</string>
<string name="failed_read_configuration">خواندن پیکربندی ناموفق بود: %s</string>
<!-- Success Messages -->
<string name="success_profile_saved">پروفایل با موفقیت ذخیره شد</string>
<string name="success_logs_saved">لاگ‌ها با موفقیت ذخیره شدند</string>
<string name="success_configuration_saved">پیکربندی ذخیره شد</string>
<string name="copied_to_clipboard">در کلیپ‌بورد کپی شد</string>
<!-- Toast Messages -->
<string name="toast_clipboard_empty">کلیپ‌بورد خالی است</string>
<string name="toast_copied_to_clipboard">در کلیپ‌بورد ذخیره شد</string>
<string name="toast_imported_from_clipboard">از کلیپ‌بورد وارد شد</string>
<!-- Scanning -->
<string name="profile_add_scan_use_front_camera">دوربین جلو</string>
<string name="profile_add_scan_use_vendor_analyzer">تحلیلگر MLKit</string>
<string name="profile_add_scan_enable_torch">چراغ‌قوه</string>
<string name="message_scanning">در حال اسکن…</string>
<string name="message_scan_app_no_apps_found">هیچ برنامهٔ منطبقی پیدا نشد</string>
<string name="message_scan_app_found">برنامه‌های زیر پیدا شدند، لطفا اقدام مورد نظر را انتخاب کنید.</string>
<!-- Icon Selection -->
<string name="icon">آیکن</string>
<string name="icon_count_format">%d آیکن</string>
<string name="no_icons_found">آیکنی پیدا نشد</string>
<string name="no_icons_match">هیچ آیکنی با \"%s\" مطابقت ندارد</string>
<string name="profile_icon">آیکن پروفایل</string>
<string name="select_icon">انتخاب آیکن</string>
<string name="select_profile_icon">انتخاب آیکن پروفایل</string>
<string name="search_icons_placeholder">جستجوی آیکن‌ها...</string>
<string name="search_icons">جستجوی آیکن‌ها</string>
<string name="close_search">بستن جستجو</string>
<string name="current_icon_format">فعلی: %s</string>
<string name="categories">دسته‌ها</string>
<string name="all_icons">همه آیکن‌ها</string>
<string name="back_to_categories">بازگشت به دسته‌ها</string>
<!-- QR Code -->
<!-- QR Stream (QRS) -->
<string name="share_as_qrs">اشتراک‌گذاری به‌صورت QR Stream</string>
<string name="qrs_fps">FPS</string>
<string name="qrs_slice_size">اندازه قطعه</string>
<string name="qrs_what_is_qrs">QRS چیست</string>
<!-- Search -->
<string name="search_placeholder">یافتن در سند</string>
<!-- Content Descriptions (Accessibility) -->
<string name="content_description_back">بازگشت</string>
<string name="content_description_scroll_to_bottom">پیمایش به پایین</string>
<string name="content_description_exit_selection_mode">خروج از حالت انتخاب</string>
<string name="content_description_copy_selected">کپی موارد انتخاب‌شده</string>
<string name="content_description_clear_search">پاک کردن جستجو</string>
<string name="content_description_qr_code">QR کد</string>
<string name="content_description_resume_logs">ادامه لاگ‌ها</string>
<string name="content_description_pause_logs">مکث لاگ‌ها</string>
<string name="content_description_collapse_search">جمع کردن جستجو</string>
<string name="content_description_search_logs">جستجوی لاگ‌ها</string>
<!-- Xposed Module -->
<string name="xposed_description">بهبود دسترسی ویژه برای sing-box</string>
<!-- Privileged Enhancement -->
<string name="privilege_module_title">ماژول‌های دسترسی ویژه</string>
<string name="lsposed_module_activated">ماژول LSPosed فعال است</string>
<string name="lsposed_module_pending_update">به‌روزرسانی ماژول LSPosed در انتظار است</string>
<string name="lsposed_module_pending_downgrade">دانگرید ماژول LSPosed در انتظار است</string>
<string name="lsposed_module_not_activated">ماژول LSPosed فعال نیست</string>
<string name="privilege_settings">بهبود دسترسی ویژه</string>
<string name="privilege_settings_hide_title">پنهان‌سازی تنظیمات</string>
<string name="privilege_settings_hide_description">مقاومت در برابر تشخیص VPN برای برنامه‌های انتخاب‌شده</string>
<string name="privilege_settings_hide_manage">مدیریت</string>
<string name="privilege_settings_hide_test">اجرای تست</string>
<string name="privilege_settings_hide_test_result">نتیجه تست</string>
<string name="privilege_settings_hide_test_running">در حال اجرای تست‌های تشخیص…</string>
<string name="privilege_settings_hide_test_not_detected">تشخیص داده نشد</string>
<string name="privilege_settings_interface_rename_title">تغییر نام رابط</string>
<string name="privilege_settings_interface_prefix">پیشوند رابط</string>
<string name="privilege_settings_vpn_detection_title">تشخیص VPN</string>
<string name="privilege_settings_view_logs">مشاهده لاگ‌ها</string>
<string name="privilege_settings_export_debug">خروجی گرفتن اطلاعات اشکال‌زدایی</string>
<string name="privilege_settings_hook_logs_empty">لاگی وجود ندارد</string>
<string name="privilege_settings_export_debug_complete">خروجی‌گیری کامل شد</string>
<string name="privilege_settings_export_debug_message">این فایل حاوی اطلاعات خصوصی است و نباید عمومی به اشتراک گذاشته شود. اندازه: %s</string>
<string name="privilege_settings_export_debug_failed">خروجی گرفتن اطلاعات اشکال‌زدایی ناموفق بود: %s</string>
<string name="privilege_settings_risky_app_title">هشدار</string>
<string name="privilege_settings_risky_vpn_message_single">این برنامه یک برنامه VPN است. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
<string name="privilege_settings_risky_vpn_message_multi">این برنامه‌ها VPN هستند. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
<string name="privilege_settings_risky_management_message_single">این برنامه مجوز مدیریت شبکه دارد. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
<string name="privilege_settings_risky_management_message_multi">این برنامه‌ها مجوز مدیریت شبکه دارند. مقاومت در برابر تشخیص VPN ممکن است مشکل ایجاد کند: %1$s</string>
<string name="privilege_module_restart_action">راه‌اندازی مجدد</string>
<string name="privilege_module_restart_failed">درخواست راه‌اندازی مجدد ناموفق بود: %1$s</string>
<string name="privilege_module_restart_notification_title">نیاز به راه‌اندازی مجدد</string>
<string name="privilege_module_restart_notification_message">ماژول LSPosed به‌روزرسانی شد. برای اعمال تغییرات دستگاه را راه‌اندازی مجدد کنید.</string>
<string name="privilege_module_restart_channel">ماژول LSPosed</string>
</resources>
@@ -0,0 +1,442 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<!-- App -->
<string name="app_name" translatable="false">sing-box</string>
<string name="app_version_title">Версия приложения</string>
<!-- Common Actions -->
<string name="ok">OK</string>
<string name="cancel">Отмена</string>
<string name="save">Сохранить</string>
<string name="discard">Отказаться</string>
<string name="edit">Редактировать</string>
<string name="close">Закрыть</string>
<string name="dismiss">Закрыть</string>
<string name="stop">Остановить</string>
<string name="reset">Сброс</string>
<string name="destroy">Уничтожить</string>
<string name="clear">Очистить</string>
<string name="browse">Просмотр</string>
<string name="search">Поиск</string>
<string name="action">Действие</string>
<string name="action_start">Начать</string>
<string name="action_deselect">Отменить выбор</string>
<string name="expand">Развернуть</string>
<string name="collapse">Свернуть</string>
<string name="expand_all">Развернуть все</string>
<string name="collapse_all">Свернуть все</string>
<string name="previous">Предыдущий</string>
<string name="next">Следующий</string>
<string name="update">Обновление</string>
<string name="read_more">Подробнее</string>
<string name="no_thanks">Нет, спасибо</string>
<string name="options">Настройки</string>
<string name="more_options">Дополнительные параметры</string>
<!-- Common States -->
<string name="enabled">Включено</string>
<string name="disabled">Выключено</string>
<string name="loading">Загрузка...</string>
<string name="calculating">Вычисление...</string>
<string name="auto">Авто</string>
<string name="success">Успешно</string>
<string name="default_text">По умолчанию</string>
<!-- Navigation Titles -->
<string name="title_dashboard">Главная</string>
<string name="title_configuration">Профили</string>
<string name="title_log">Логи</string>
<string name="title_settings">Параметры</string>
<string name="title_app_settings">Приложение</string>
<string name="title_new_profile">Новый профиль</string>
<string name="title_edit_profile">Редактировать профиль</string>
<string name="title_edit_configuration">Редактировать конфигурацию</string>
<string name="title_groups">Группы</string>
<string name="title_debug">Отладка</string>
<string name="title_connections">Соединения</string>
<string name="title_others">Другое</string>
<string name="title_scan_result">Результат сканирования</string>
<!-- Status -->
<string name="status_default">Служба не запущена</string>
<string name="status_starting">Запуск</string>
<string name="status_stopping">Остановка</string>
<string name="status_started">Запущена</string>
<!-- Dashboard -->
<string name="dashboard_items">Элементы главной страницы</string>
<string name="memory">Память</string>
<string name="goroutines">Подпрограммы</string>
<string name="upload">Отправка</string>
<string name="download">Получение</string>
<string name="connections_in">Входящие</string>
<string name="connections_out">Исходящие</string>
<string name="clash_mode">Режим Clash</string>
<string name="mode">Режим</string>
<string name="system_proxy">Системный прокси</string>
<string name="url_test">Тест</string>
<string name="drag_handle_to_reorder">Перетащите ползунок, чтобы изменить порядок элементов</string>
<string name="drag_to_reorder">Перетащите, чтобы изменить порядок</string>
<string name="reset_order">Сбросить порядок</string>
<!-- Connections -->
<string name="empty_connections">Нет подключений</string>
<string name="search_connections">Поиск подключений...</string>
<string name="close_connections_confirm">Закрыть все соединения?</string>
<string name="connection_state_all">Все</string>
<string name="connection_state_active">Активно</string>
<string name="connection_state_closed">Закрыто</string>
<string name="connection_sort_date">Дата</string>
<string name="connection_sort_traffic">Трафик</string>
<string name="connection_sort_traffic_total">Общий трафик</string>
<string name="connection_close">Закрыть</string>
<string name="connection_close_all">Закрыть все</string>
<string name="connection_state">Состояние</string>
<string name="connection_details">Данные подключения</string>
<string name="connection_section_basic">Основная информация</string>
<string name="connection_section_metadata">Метаданные</string>
<string name="connection_section_process">Информация о процессе</string>
<string name="connection_created_at">Создано в</string>
<string name="connection_closed_at">Закрыто в</string>
<string name="connection_duration">Продолжительность</string>
<string name="connection_uplink">Восходящий канал</string>
<string name="connection_downlink">Нисходящий канал</string>
<string name="connection_inbound">Входящий</string>
<string name="connection_inbound_type">Входящий тип</string>
<string name="connection_outbound">Исходящий</string>
<string name="connection_outbound_type">Исходящий тип</string>
<string name="connection_ip_version">Версия IP</string>
<string name="connection_network">Сеть</string>
<string name="connection_source">Источник</string>
<string name="connection_destination">Место назначения</string>
<string name="connection_domain">Домен</string>
<string name="connection_protocol">Протокол</string>
<string name="connection_user">Пользователь</string>
<string name="connection_from_outbound">Из исходящих</string>
<string name="connection_match_rule">Правило соответствия</string>
<string name="connection_chain">Цепь</string>
<string name="connection_process_id">ID процесса</string>
<string name="connection_user_id">ID пользователя</string>
<string name="connection_user_name">Имя пользователя</string>
<string name="connection_process_path">Путь процесса</string>
<string name="connection_package_name">Название пакета</string>
<!-- Profiles -->
<string name="no_profiles">Профили не настроены</string>
<string name="add_profile">Добавить профиль</string>
<string name="update_profile">Обновить профиль</string>
<string name="import_remote_profile">Импортировать удалённый профиль</string>
<string name="import_remote_profile_message">Импортировать удалённый профиль %1$s? Для загрузки конфигурации будет установлено соединение с %2$s.</string>
<string name="import_from_file_description">Импорт конфигурации из локального файла</string>
<string name="scan_qr_code_description">Сканирование QR-кода с конфигурацией</string>
<string name="create_new_profile_description">Создать новый профиль с нуля</string>
<string name="icloud_profile_unsupported">Профили iCloud не поддерживаются на текущей платформе</string>
<string name="profile_name">Имя</string>
<string name="profile_type">Тип</string>
<string name="profile_source">Источник</string>
<string name="profile_url" translatable="false">URL</string>
<string name="profile_import_file">Импортировать файл</string>
<string name="profile_create">Создать</string>
<string name="profile_share_url">Поделиться URL в виде QR-кода</string>
<string name="profile_input_required">Обязательно</string>
<string name="profile_update">Обновить</string>
<string name="profile_auto_update">Автообновление</string>
<string name="profile_auto_update_interval">Интервал автообновления (минуты)</string>
<string name="profile_auto_update_interval_minimum_hint">Минимальное значение - 15</string>
<string name="profile_type_local">Локальный</string>
<string name="profile_type_remote">Удалённый</string>
<string name="profile_type_remote_updated">Удалённый • %s</string>
<string name="profile_source_create_new">Создать новый</string>
<string name="profile_source_import">Импорт</string>
<string name="profile_add_import_file">Импорт из файла</string>
<string name="profile_add_scan_qr_code">Сканировать QR-код</string>
<string name="profile_add_create_manually">Создать вручную</string>
<string name="profile_override">Перезапись профиля</string>
<string name="basic_information">Основная информация</string>
<string name="remote_configuration">Удалённая конфигурация</string>
<string name="last_updated_format">Последнее обновление: %s</string>
<string name="content">Содержимое</string>
<string name="json_viewer">Просмотр JSON</string>
<string name="json_editor">Редактор JSON</string>
<string name="view_configuration">Просмотреть конфигурацию</string>
<string name="save_as_file">Сохранить как файл</string>
<string name="share_as_file">Поделиться как файлом</string>
<string name="save_content_json">Сохранить содержимое в файл JSON</string>
<string name="share_content_json">Поделиться содержимым в файле JSON</string>
<string name="unsaved_changes">Несохранённые изменения</string>
<string name="unsaved_changes_message">Есть несохранённые изменения. Отменить их?</string>
<string name="import_profile_confirm_title">Импорт профиля</string>
<string name="import_profile_confirm_message">Импортировать профиль \'%s\'?</string>
<string name="import_action">Импортировать</string>
<!-- Groups -->
<string name="group_selected_title">Выбрано</string>
<!-- Log -->
<string name="log_level">Уровень логов</string>
<string name="filter_label">Фильтр: %s</string>
<string name="clear_filter">Очистить</string>
<string name="clear_logs">Очистить логи</string>
<string name="search_logs_placeholder">Поиск в логах...</string>
<string name="logs_copied_to_clipboard">Лог скопирован в буфер обмена</string>
<string name="no_logs_to_copy">Нет записей для копирования</string>
<string name="no_logs_to_share">Нет записей для отправки</string>
<string name="intent_share_logs">Отправить лог</string>
<string name="selected_count">Выбрано: %d</string>
<string name="not_selected">Не выбрано</string>
<string name="save_to_clipboard">В буфер обмена</string>
<string name="save_to_file">В файл</string>
<!-- Settings -->
<string name="service">Служба</string>
<string name="core">Ядро</string>
<string name="core_version_title">Версия ядра</string>
<string name="core_data_size">Размер данных</string>
<string name="about">О приложении</string>
<string name="source_code">Исходный код</string>
<string name="sponsor">Поддержать</string>
<string name="working_directory">Рабочая директория</string>
<string name="disable_deprecated_warnings">Отключить предупреждения об устаревании</string>
<string name="ignore_memory_limit">Игнорировать ограничение памяти</string>
<string name="ignore_memory_limit_description">Не применять ограничения по памяти для sing-box.</string>
<string name="auto_redirect">Автоматическое перенаправление</string>
<string name="auto_redirect_description">Требуются права ROOT</string>
<string name="system_http_proxy">Системный HTTP-прокси</string>
<string name="update_settings">Настройки обновлений</string>
<!-- Per-App Proxy -->
<string name="per_app_proxy">Прокси для отдельных приложений</string>
<string name="per_app_proxy_mode">Режим прокси</string>
<string name="per_app_proxy_mode_include">Включить</string>
<string name="per_app_proxy_mode_include_description">Только выбранные приложения будут использовать VPN</string>
<string name="per_app_proxy_mode_exclude">Исключить</string>
<string name="per_app_proxy_mode_exclude_description">Выбранные приложения не будут использовать VPN</string>
<string name="per_app_proxy_action_copy">Копировать</string>
<string name="per_app_proxy_action_copy_package_name">Имя пакета</string>
<string name="per_app_proxy_action_copy_uid">Уникальный ID</string>
<string name="per_app_proxy_sort_mode">Сортировка</string>
<string name="per_app_proxy_sort_mode_name">По имени</string>
<string name="per_app_proxy_sort_mode_package_name">По имени пакета</string>
<string name="per_app_proxy_sort_mode_uid">По уникальному ID</string>
<string name="per_app_proxy_sort_mode_install_time">По дате установки</string>
<string name="per_app_proxy_sort_mode_update_time">По дате обновления</string>
<string name="per_app_proxy_sort_mode_reverse">В обратном порядке</string>
<string name="per_app_proxy_filter">Фильтр</string>
<string name="per_app_proxy_hide_system_apps">Скрыть системные приложения</string>
<string name="per_app_proxy_hide_offline_apps">Скрыть оффлайн-приложения</string>
<string name="per_app_proxy_hide_disabled_apps">Скрыть отключённые приложения</string>
<string name="per_app_proxy_select">Выбрать</string>
<string name="per_app_proxy_select_all">Выбрать все</string>
<string name="per_app_proxy_select_none">Снять выделение</string>
<string name="per_app_proxy_backup">Резервная копия</string>
<string name="per_app_proxy_import">Импорт из буфера обмена</string>
<string name="per_app_proxy_export">Экспорт в буфер обмена</string>
<string name="per_app_proxy_scan">Сканирование</string>
<string name="per_app_proxy_scan_china_apps">Китайские приложения</string>
<string name="per_app_proxy_manage">Управление</string>
<string name="content_description_app_icon">Значок приложения</string>
<string name="per_app_proxy_managed_mode">Управляемый режим</string>
<string name="per_app_proxy_managed_mode_description">Автоматически исключать китайские приложения</string>
<string name="per_app_proxy_package_query_mode">Режим</string>
<string name="per_app_proxy_shizuku_required">При установке из Play Store для работы прокси для каждого приложения требуется Shizuku. Google Play не позволяет нам использовать разрешение QUERY_ALL_PACKAGES (хотя другим подобным приложениям это разрешено), которое необходимо для отображения списка приложений.</string>
<string name="per_app_proxy_root_required">При установке из Play Store для функции прокси для отдельных приложений требуются права ROOT. Google Play не разрешает нам использовать разрешение QUERY_ALL_PACKAGES (хотя другим подобным приложениям это разрешено), которое необходимо для отображения списка приложений.</string>
<string name="privileged_access_required">Для полного списка приложений необходимы права ROOT или доступ через Shizuku</string>
<!-- Permissions -->
<string name="background_permission">Фоновое разрешение</string>
<string name="background_permission_description">Предоставить необходимые разрешения для корректной работы VPN.\n\nЕсли Вы используете устройство китайского производителя, уведомление может не исчезнуть даже после предоставления разрешения.</string>
<string name="request_background_permission">Игнорировать оптимизацию батареи</string>
<string name="location_permission_title">Разрешение на определение местоположения</string>
<string name="location_permission_description">Ваш профиль содержит правила маршрутизации &lt;strong&gt;&lt;tt&gt;wifi_ssid&lt;/tt&gt; или &lt;tt&gt;wifi_bssid&lt;/tt&gt;&lt;/strong&gt;. Для их работы sing-box использует разрешение на &lt;strong&gt;определение местоположения&lt;/strong&gt; &lt;strong&gt;в фоновом режиме&lt;/strong&gt;, чтобы получить информацию о подключённой сети Wi-Fi. Эта информация будет использоваться &lt;strong&gt;исключительно для маршрутизации&lt;/strong&gt;.</string>
<string name="location_permission_background_description">В Android 10 и новее требуется разрешение на &lt;strong&gt;определение местоположения в фоне&lt;/strong&gt;. Выберите &lt;strong&gt;«Разрешить всегда»&lt;/strong&gt; для предоставления этого разрешения.</string>
<string name="notification_permission_title">Разрешение на уведомления</string>
<string name="notification_permission_required_description">Без разрешения на отправку уведомлений sing-box не сможет отображать скорость сети в реальном времени. Предоставьте разрешение или отключите уведомления о скорости перед запуском службы.</string>
<string name="root_access_required">Требуются права ROOT</string>
<string name="root_access_denied">Доступ ROOT запрещен</string>
<!-- Updates -->
<string name="check_update">Проверить обновления</string>
<string name="check_update_automatic">Автоматическая проверка обновлений</string>
<string name="check_update_prompt_play">Включить автоматическую проверку обновлений через **Google Play**?</string>
<string name="check_update_prompt_github">Включить автоматическую проверку обновлений через **GitHub**?</string>
<string name="update_track">Канал обновлений</string>
<string name="update_track_stable">Стабильный</string>
<string name="update_track_beta">Бета</string>
<string name="update_track_not_supported">Этот канал пока не поддерживает проверку обновлений</string>
<string name="view_release">Посмотреть релиз</string>
<string name="downloading">Загрузка...</string>
<string name="exporting">Экспорт...</string>
<string name="no_updates_available">Обновлений нет</string>
<string name="new_version_available">Доступна новая версия: %s</string>
<string name="auto_update">Автообновление</string>
<string name="auto_update_description">Автоматически загружать и устанавливать обновления в фоне</string>
<!-- Silent Install -->
<string name="silent_install">Тихая установка</string>
<string name="silent_install_description">Устанавливать обновления без Вашего участия</string>
<string name="silent_install_method">Метод установки</string>
<string name="silent_install_verify_failed">%s недоступен или доступ запрещен</string>
<string name="install_method_package_installer">PackageInstaller</string>
<string name="install_method_shizuku">Shizuku</string>
<string name="install_method_root">ROOT</string>
<string name="package_installer_not_available">Разрешение на установку не предоставлено</string>
<string name="grant_install_permission">Предоставить разрешение на установку</string>
<string name="grant_install_permission_description">Разрешить установку приложений из этого источника</string>
<!-- Shizuku -->
<string name="shizuku_not_available">Shizuku не установлен или не запущен</string>
<string name="shizuku_description">Shizuku позволяет приложениям напрямую использовать системные API с повышенными привилегиями</string>
<string name="get_shizuku">Установить Shizuku</string>
<string name="start_shizuku">Запустить Shizuku</string>
<string name="request_shizuku">Запросить Shizuku</string>
<!-- Time -->
<string name="time_just_now">Только что</string>
<string name="time_yesterday">Вчера</string>
<string name="time_now">Сейчас</string>
<string name="time_yesterday_short">1 день</string>
<string name="time_minutes_short">%d мин.</string>
<string name="time_hours_short">%d ч.</string>
<string name="time_days_short">%d дн.</string>
<plurals name="time_minutes_ago">
<item quantity="one">%d минуту назад</item>
<item quantity="few">%d минуты назад</item>
<item quantity="many">%d минут назад</item>
<item quantity="other">%d минут назад</item>
</plurals>
<plurals name="time_hours_ago">
<item quantity="one">%d час назад</item>
<item quantity="few">%d часа назад</item>
<item quantity="many">%d часов назад</item>
<item quantity="other">%d часов назад</item>
</plurals>
<plurals name="time_days_ago">
<item quantity="one">%d день назад</item>
<item quantity="few">%d дня назад</item>
<item quantity="many">%d дней назад</item>
<item quantity="other">%d дней назад</item>
</plurals>
<!-- Menu -->
<string name="menu_undo">Отменить</string>
<string name="menu_redo">Повторить</string>
<string name="menu_format">Форматировать</string>
<string name="menu_delete">Удалить</string>
<string name="menu_share">Поделиться</string>
<!-- Errors -->
<string name="error_title">Ошибка</string>
<string name="error_missing_vpn_permission">Отсутствует разрешение на VPN</string>
<string name="error_empty_configuration">Пустая конфигурация</string>
<string name="error_empty_file">Пустой файл</string>
<string name="error_decode_profile">Не удалось декодировать профиль: %s</string>
<string name="error_invalid_configuration">Недопустимая конфигурация sing-box: %s</string>
<string name="error_start_command_server">Запуск сервера команд</string>
<string name="error_create_service">Создание службы</string>
<string name="error_start_service">Запуск службы</string>
<string name="error_deprecated_warning">Предупреждение об устаревании</string>
<string name="error_deprecated_documentation">Документация</string>
<string name="file_manager_missing">На Вашем устройстве отсутствует стандартный файловый менеджер Android. Установите, например, Material Files.</string>
<string name="no_file_manager">Файловый менеджер не найден</string>
<!-- Failed Operations -->
<string name="failed_save_profile">Не удалось сохранить профиль: %s</string>
<string name="failed_save_logs">Не удалось сохранить лог: %s</string>
<string name="failed_share_logs">Не удалось отправить лог: %s</string>
<string name="failed_read_configuration">Не удалось прочитать конфигурацию: %s</string>
<!-- Success Messages -->
<string name="success_profile_saved">Профиль успешно сохранён</string>
<string name="success_logs_saved">Лог успешно сохранён</string>
<string name="success_configuration_saved">Конфигурация сохранена</string>
<string name="copied_to_clipboard">Скопировано в буфер обмена</string>
<!-- Toast Messages -->
<string name="toast_clipboard_empty">Буфер обмена пуст</string>
<string name="toast_copied_to_clipboard">Экспортировано в буфер обмена</string>
<string name="toast_imported_from_clipboard">Импортировано из буфера обмена</string>
<!-- Scanning -->
<string name="profile_add_scan_use_front_camera">Фронтальная камера</string>
<string name="profile_add_scan_use_vendor_analyzer">Анализатор MLKit</string>
<string name="profile_add_scan_enable_torch">Фонарик</string>
<string name="message_scanning">Сканирование…</string>
<string name="message_scan_app_no_apps_found">Совпадающих приложений не найдено</string>
<string name="message_scan_app_found">Найдены следующие приложения. Выберите нужное действие.</string>
<!-- Icon Selection -->
<string name="icon">Иконка</string>
<string name="icon_count_format">%d иконок</string>
<string name="no_icons_found">Иконки не найдены</string>
<string name="no_icons_match">Нет иконок, соответствующих \'%s\'</string>
<string name="profile_icon">Иконка профиля</string>
<string name="select_icon">Выбрать иконку</string>
<string name="select_profile_icon">Выбрать иконку профиля</string>
<string name="search_icons_placeholder">Поиск иконок...</string>
<string name="search_icons">Поиск иконок</string>
<string name="close_search">Закрыть поиск</string>
<string name="current_icon_format">Текущая: %s</string>
<string name="categories">Категории</string>
<string name="all_icons">Все иконки</string>
<string name="back_to_categories">Назад к категориям</string>
<!-- QR Code -->
<!-- QR Stream (QRS) -->
<string name="share_as_qrs">Отправить как QR Stream</string>
<string name="qrs_fps">Кадров/сек.</string>
<string name="qrs_slice_size">Размер фрагмента</string>
<string name="qrs_what_is_qrs">Что такое QRS</string>
<!-- Search -->
<string name="search_placeholder">Найти в документе</string>
<!-- Content Descriptions (Accessibility) -->
<string name="content_description_back">Назад</string>
<string name="content_description_scroll_to_bottom">Прокрутить вниз</string>
<string name="content_description_exit_selection_mode">Выйти из режима выбора</string>
<string name="content_description_copy_selected">Копировать выделенное</string>
<string name="content_description_clear_search">Очистить поиск</string>
<string name="content_description_qr_code">QR-код</string>
<string name="content_description_resume_logs">Возобновить лог</string>
<string name="content_description_pause_logs">Приостановить лог</string>
<string name="content_description_collapse_search">Свернуть поиск</string>
<string name="content_description_search_logs">Поиск в логе</string>
<!-- Xposed Module -->
<string name="xposed_description">Привилегированное расширение для sing-box</string>
<!-- Privileged Enhancement -->
<string name="privilege_module_title">Привилегированные модули</string>
<string name="lsposed_module_activated">Модуль LSPosed активирован</string>
<string name="lsposed_module_pending_update">Ожидается обновление модуля LSPosed</string>
<string name="lsposed_module_pending_downgrade">Ожидается откат модуля LSPosed</string>
<string name="lsposed_module_not_activated">Модуль LSPosed не активирован</string>
<string name="privilege_settings">Привилегированное расширение</string>
<string name="privilege_settings_hide_title">Скрытие настроек</string>
<string name="privilege_settings_hide_description">Защита от обнаружения VPN для выбранных приложений</string>
<string name="privilege_settings_hide_manage">Управление</string>
<string name="privilege_settings_hide_test">Запустить тест</string>
<string name="privilege_settings_hide_test_result">Результат теста</string>
<string name="privilege_settings_hide_test_running">Выполняются тесты на обнаружение...</string>
<string name="privilege_settings_hide_test_not_detected">Не обнаружено</string>
<string name="privilege_settings_interface_rename_title">Переименование интерфейса</string>
<string name="privilege_settings_interface_prefix">Префикс интерфейса</string>
<string name="privilege_settings_vpn_detection_title">Обнаружение VPN</string>
<string name="privilege_settings_view_logs">Просмотреть журнал</string>
<string name="privilege_settings_export_debug">Экспорт отладочной информации</string>
<string name="privilege_settings_hook_logs_empty">Журнал пуст</string>
<string name="privilege_settings_export_debug_complete">Экспорт завершён</string>
<string name="privilege_settings_export_debug_message">Файл содержит конфиденциальную информацию и не должен распространяться публично. Размер: %s</string>
<string name="privilege_settings_export_debug_failed">Не удалось экспортировать отладочную информацию: %s</string>
<string name="privilege_settings_risky_app_title">Предупреждение</string>
<string name="privilege_settings_risky_vpn_message_single">Это приложение - VPN-клиент. Скрытие VPN может вызвать проблемы: %1$s</string>
<string name="privilege_settings_risky_vpn_message_multi">Эти приложения - VPN-клиенты. Скрытие VPN может вызвать проблемы: %1$s</string>
<string name="privilege_settings_risky_management_message_single">Это приложение имеет права управления сетью. Скрытие VPN может вызвать проблемы: %1$s</string>
<string name="privilege_settings_risky_management_message_multi">Эти приложения имеют права управления сетью. Скрытие VPN может вызвать проблемы: %1$s</string>
<string name="privilege_module_restart_action">Перезагрузить</string>
<string name="privilege_module_restart_failed">Не удалось запросить перезагрузку: %1$s</string>
<string name="privilege_module_restart_notification_title">Требуется перезагрузка</string>
<string name="privilege_module_restart_notification_message">Модуль LSPosed обновлён. Перезагрузите устройство, чтобы применить изменения.</string>
<string name="privilege_module_restart_channel">Модуль LSPosed</string>
</resources>
@@ -1,6 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<!-- App -->
<string name="app_name" translatable="false">sing-box</string>
<string name="app_version_title">应用版本</string>
<!-- Common Actions -->
@@ -124,7 +125,6 @@
<string name="no_profiles">没有配置的配置文件</string>
<string name="add_profile">添加配置文件</string>
<string name="update_profile">更新配置文件</string>
<string name="share_profile">分享配置文件</string>
<string name="import_remote_profile">导入远程配置</string>
<string name="import_remote_profile_message">您确定要导入远程配置文件 %1$s 吗?您将连接到 %2$s 来下载配置。</string>
<string name="import_from_file_description">从本地文件导入配置</string>
@@ -134,9 +134,9 @@
<string name="profile_name">名称</string>
<string name="profile_type">类型</string>
<string name="profile_source"></string>
<string name="profile_url" translatable="false">URL</string>
<string name="profile_import_file">导入文件</string>
<string name="profile_create">创建</string>
<string name="profile_share">分享</string>
<string name="profile_share_url">通过二维码分享 URL</string>
<string name="profile_input_required">必须</string>
<string name="profile_update">更新</string>
@@ -165,7 +165,6 @@
<string name="share_content_json">分享配置 JSON 文件</string>
<string name="unsaved_changes">未保存的更改</string>
<string name="unsaved_changes_message">您有未保存的更改。要放弃它们吗?</string>
<string name="profile_qr_code_text">配置文件二维码:%s</string>
<string name="import_profile_confirm_title">导入配置</string>
<string name="import_profile_confirm_message">导入配置「%s」?</string>
<string name="import_action">导入</string>
@@ -266,6 +265,7 @@
<string name="update_track_not_supported">当前轨道尚不支持检查更新</string>
<string name="view_release">查看发布</string>
<string name="downloading">下载中…</string>
<string name="exporting">导出中…</string>
<string name="no_updates_available">没有可用的更新</string>
<string name="new_version_available">有新版本可用:%s</string>
<string name="auto_update">自动更新</string>
@@ -273,7 +273,6 @@
<!-- Silent Install -->
<string name="silent_install">静默安装</string>
<string name="silent_install_title">静默安装</string>
<string name="silent_install_description">无需交互即可安装更新</string>
<string name="silent_install_method">安装方式</string>
<string name="silent_install_verify_failed">%s 不可用或权限被拒绝</string>
@@ -335,8 +334,6 @@
<string name="failed_save_profile">保存配置文件失败:%s</string>
<string name="failed_save_logs">保存日志失败:%s</string>
<string name="failed_share_logs">分享日志失败:%s</string>
<string name="failed_save_qr_code">保存二维码失败:%s</string>
<string name="failed_share_qr_code">分享二维码失败:%s</string>
<string name="failed_read_configuration">读取配置失败:%s</string>
<!-- Success Messages -->
@@ -344,7 +341,6 @@
<string name="success_logs_saved">日志保存成功</string>
<string name="success_configuration_saved">配置已保存</string>
<string name="copied_to_clipboard">已复制到剪贴板</string>
<string name="qr_code_saved_to_gallery">二维码已保存到相册</string>
<!-- Toast Messages -->
<string name="toast_clipboard_empty">剪切板为空</string>
@@ -376,16 +372,10 @@
<string name="back_to_categories">返回分类</string>
<!-- QR Code -->
<string name="intent_share_qr_code">分享二维码</string>
<!-- QR Stream (QRS) -->
<string name="share_as_qrs">分享为 QRS</string>
<string name="qrs_progress">接收中:%1$d / %2$d 块</string>
<string name="qrs_speed">速度</string>
<string name="qrs_interval_ms">间隔:%d 毫秒</string>
<string name="qrs_scanning_mode">QRS 模式</string>
<string name="qrs_fps">帧率</string>
<string name="qrs_fps_interval">%d 毫秒)</string>
<string name="qrs_slice_size">分块大小</string>
<string name="qrs_what_is_qrs">什么是 QRS</string>
@@ -403,6 +393,9 @@
<string name="content_description_pause_logs">暂停日志</string>
<string name="content_description_collapse_search">折叠搜索</string>
<string name="content_description_search_logs">搜索日志</string>
<!-- Xposed Module -->
<string name="xposed_description">sing-box 的特权增强</string>
<!-- Privileged Enhancement -->
<string name="privilege_settings">特权增强</string>
<string name="privilege_module_title">特权模块</string>
@@ -413,8 +406,6 @@
<string name="privilege_settings_hide_test_result">测试结果</string>
<string name="privilege_settings_hide_test_running">正在运行检测测试…</string>
<string name="privilege_settings_hide_test_not_detected">未检测到</string>
<string name="privilege_settings_test_interfaces">接口:</string>
<string name="privilege_settings_test_http_proxy">HTTP 代理:</string>
<string name="privilege_settings_interface_rename_title">接口重命名</string>
<string name="privilege_settings_interface_prefix">接口前缀</string>
<string name="privilege_settings_vpn_detection_title">VPN 检测</string>
@@ -429,14 +420,11 @@
<string name="privilege_settings_risky_vpn_message_multi">这些应用是 VPN 应用,抵抗 VPN 检测可能造成问题:%1$s</string>
<string name="privilege_settings_risky_management_message_single">该应用具有网络管理权限,抵抗 VPN 检测可能造成问题:%1$s</string>
<string name="privilege_settings_risky_management_message_multi">这些应用具有网络管理权限,抵抗 VPN 检测可能造成问题:%1$s</string>
<string name="privilege_module_restart_title">需要重启</string>
<string name="privilege_module_restart_message">LSPosed 模块已更新,请重启以应用改动。</string>
<string name="privilege_module_restart_action">重新启动</string>
<string name="privilege_module_restart_failed">请求重启失败:%1$s</string>
<string name="privilege_module_restart_notification_title">需要重启</string>
<string name="privilege_module_restart_notification_message">LSPosed 模块已更新,请重启以应用改动。</string>
<string name="privilege_module_restart_channel">LSPosed 模块</string>
<string name="lsposed_module">LSPosed 模块</string>
<string name="lsposed_module_activated">LSPosed 模块已激活</string>
<string name="lsposed_module_pending_update">LSPosed 模块待更新</string>
<string name="lsposed_module_pending_downgrade">LSPosed 模块待降级</string>
@@ -0,0 +1,435 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<!-- App -->
<string name="app_name" translatable="false">sing-box</string>
<string name="app_version_title">應用程式版本</string>
<!-- Common Actions -->
<string name="ok">確定</string>
<string name="cancel">取消</string>
<string name="save">儲存</string>
<string name="discard">放棄</string>
<string name="edit">編輯</string>
<string name="close">關閉</string>
<string name="dismiss">關閉</string>
<string name="stop">停止</string>
<string name="reset">重設</string>
<string name="destroy">銷毀</string>
<string name="clear">清除</string>
<string name="browse">瀏覽</string>
<string name="search">搜尋</string>
<string name="action">操作</string>
<string name="action_start">啟動</string>
<string name="action_deselect">取消選擇</string>
<string name="expand">展開</string>
<string name="collapse">收合</string>
<string name="expand_all">全部展開</string>
<string name="collapse_all">全部收合</string>
<string name="previous">上一個</string>
<string name="next">下一個</string>
<string name="update">更新</string>
<string name="read_more">閱讀更多</string>
<string name="no_thanks">不,謝謝</string>
<string name="options">選項</string>
<string name="more_options">更多選項</string>
<!-- Common States -->
<string name="enabled">啟用</string>
<string name="disabled">停用</string>
<string name="loading">載入中...</string>
<string name="calculating">計算中...</string>
<string name="auto">自動</string>
<string name="success">成功</string>
<string name="default_text">預設</string>
<!-- Navigation Titles -->
<string name="title_dashboard">儀表板</string>
<string name="title_configuration">設定檔</string>
<string name="title_log">日誌</string>
<string name="title_settings">設定</string>
<string name="title_app_settings">應用程式</string>
<string name="title_new_profile">新增設定檔</string>
<string name="title_edit_profile">編輯設定檔</string>
<string name="title_edit_configuration">編輯設定</string>
<string name="title_groups">群組</string>
<string name="title_debug">偵錯</string>
<string name="title_connections">連線</string>
<string name="title_others">其他</string>
<string name="title_scan_result">掃描結果</string>
<!-- Status -->
<string name="status_default">服務未啟動</string>
<string name="status_starting">啟動中</string>
<string name="status_stopping">停止中</string>
<string name="status_started">已啟動</string>
<!-- Dashboard -->
<string name="dashboard_items">儀表板項目</string>
<string name="memory">記憶體</string>
<string name="goroutines">協程</string>
<string name="upload">上傳</string>
<string name="download">下載</string>
<string name="connections_in">入站</string>
<string name="connections_out">出站</string>
<string name="clash_mode">Clash 模式</string>
<string name="mode">模式</string>
<string name="system_proxy">系統代理</string>
<string name="url_test">測試</string>
<string name="drag_handle_to_reorder">拖曳控制柄以重新排序項目</string>
<string name="drag_to_reorder">拖曳以重新排序</string>
<string name="reset_order">重設順序</string>
<!-- Connections -->
<string name="empty_connections">無連線</string>
<string name="search_connections">搜尋連線…</string>
<string name="close_connections_confirm">關閉所有連線?</string>
<string name="connection_state_all">全部</string>
<string name="connection_state_active">活躍</string>
<string name="connection_state_closed">已關閉</string>
<string name="connection_sort_date">日期</string>
<string name="connection_sort_traffic">流量</string>
<string name="connection_sort_traffic_total">總流量</string>
<string name="connection_close">關閉</string>
<string name="connection_close_all">全部關閉</string>
<string name="connection_state">狀態</string>
<string name="connection_details">連線詳情</string>
<string name="connection_section_basic">基本資訊</string>
<string name="connection_section_metadata">後設資料</string>
<string name="connection_section_process">行程資訊</string>
<string name="connection_created_at">建立時間</string>
<string name="connection_closed_at">關閉時間</string>
<string name="connection_duration">持續時間</string>
<string name="connection_uplink">上行</string>
<string name="connection_downlink">下行</string>
<string name="connection_inbound">入站</string>
<string name="connection_inbound_type">入站類型</string>
<string name="connection_outbound">出站</string>
<string name="connection_outbound_type">出站類型</string>
<string name="connection_ip_version">IP 版本</string>
<string name="connection_network">網路</string>
<string name="connection_source">來源</string>
<string name="connection_destination">目標</string>
<string name="connection_domain">網域</string>
<string name="connection_protocol">協定</string>
<string name="connection_user">使用者</string>
<string name="connection_from_outbound">來自出站</string>
<string name="connection_match_rule">比對規則</string>
<string name="connection_chain"></string>
<string name="connection_process_id">行程 ID</string>
<string name="connection_user_id">使用者 ID</string>
<string name="connection_user_name">使用者名稱</string>
<string name="connection_process_path">行程路徑</string>
<string name="connection_package_name">套件名稱</string>
<!-- Profiles -->
<string name="no_profiles">尚未設定任何設定檔</string>
<string name="add_profile">新增設定檔</string>
<string name="update_profile">更新設定檔</string>
<string name="import_remote_profile">匯入遠端設定</string>
<string name="import_remote_profile_message">您確定要匯入遠端設定檔 %1$s 嗎?您將連線到 %2$s 以下載設定。</string>
<string name="import_from_file_description">從本機檔案匯入設定</string>
<string name="scan_qr_code_description">掃描設定 QR 碼</string>
<string name="create_new_profile_description">從零建立新的設定檔</string>
<string name="icloud_profile_unsupported">目前平台不支援 iCloud 設定檔</string>
<string name="profile_name">名稱</string>
<string name="profile_type">類型</string>
<string name="profile_source">來源</string>
<string name="profile_url" translatable="false">URL</string>
<string name="profile_import_file">匯入檔案</string>
<string name="profile_create">建立</string>
<string name="profile_share_url">透過二維碼分享 URL</string>
<string name="profile_input_required">必填</string>
<string name="profile_update">更新</string>
<string name="profile_auto_update">自動更新</string>
<string name="profile_auto_update_interval">自動更新間隔 (分)</string>
<string name="profile_auto_update_interval_minimum_hint">最低值為 15</string>
<string name="profile_type_local">本地</string>
<string name="profile_type_remote">遠端</string>
<string name="profile_type_remote_updated">遠端 • %s</string>
<string name="profile_source_create_new">建立新項目</string>
<string name="profile_source_import">匯入</string>
<string name="profile_add_import_file">從檔案匯入</string>
<string name="profile_add_scan_qr_code">掃描二維碼</string>
<string name="profile_add_create_manually">手動建立</string>
<string name="profile_override">設定覆寫</string>
<string name="basic_information">基本資訊</string>
<string name="remote_configuration">遠端設定</string>
<string name="last_updated_format">最後更新:%s</string>
<string name="content">內容</string>
<string name="json_viewer">JSON 檢視器</string>
<string name="json_editor">JSON 編輯器</string>
<string name="view_configuration">檢視設定</string>
<string name="save_as_file">儲存為檔案</string>
<string name="share_as_file">分享為檔案</string>
<string name="save_content_json">儲存設定 JSON 檔案</string>
<string name="share_content_json">分享設定 JSON 檔案</string>
<string name="unsaved_changes">未儲存的更改</string>
<string name="unsaved_changes_message">您有未儲存的更改。要放棄它們嗎?</string>
<string name="import_profile_confirm_title">匯入設定檔</string>
<string name="import_profile_confirm_message">匯入設定檔「%s」?</string>
<string name="import_action">匯入</string>
<!-- Groups -->
<string name="group_selected_title">已選取</string>
<!-- Log -->
<string name="log_level">日誌等級</string>
<string name="filter_label">篩選器:%s</string>
<string name="clear_filter">清除</string>
<string name="clear_logs">清除日誌</string>
<string name="search_logs_placeholder">搜尋日誌…</string>
<string name="logs_copied_to_clipboard">日誌已複製到剪貼簿</string>
<string name="no_logs_to_copy">沒有日誌可複製</string>
<string name="no_logs_to_share">沒有日誌可分享</string>
<string name="intent_share_logs">分享日誌</string>
<string name="selected_count">已選擇 %d 項</string>
<string name="not_selected">未選擇</string>
<string name="save_to_clipboard">複製到剪貼簿</string>
<string name="save_to_file">儲存到檔案</string>
<!-- Settings -->
<string name="service">服務</string>
<string name="core">核心</string>
<string name="core_version_title">核心版本</string>
<string name="core_data_size">資料大小</string>
<string name="about">關於</string>
<string name="source_code">原始碼</string>
<string name="sponsor">贊助</string>
<string name="working_directory">工作目錄</string>
<string name="disable_deprecated_warnings">停用過時警告</string>
<string name="ignore_memory_limit">忽略記憶體限制</string>
<string name="ignore_memory_limit_description">不對 sing-box 強制執行記憶體限制。</string>
<string name="auto_redirect">自動重定向</string>
<string name="auto_redirect_description">需要 ROOT 權限</string>
<string name="system_http_proxy">系統 HTTP 代理</string>
<string name="update_settings">更新設定</string>
<!-- Per-App Proxy -->
<string name="per_app_proxy">每個應用程式代理</string>
<string name="per_app_proxy_mode">代理模式</string>
<string name="per_app_proxy_mode_include">包含</string>
<string name="per_app_proxy_mode_include_description">僅允許選定的應用程式透過 VPN</string>
<string name="per_app_proxy_mode_exclude">排除</string>
<string name="per_app_proxy_mode_exclude_description">選定的應用將從 VPN 中排除</string>
<string name="per_app_proxy_action_copy">複製</string>
<string name="per_app_proxy_action_copy_package_name">套件名稱</string>
<string name="per_app_proxy_action_copy_uid">UID</string>
<string name="per_app_proxy_sort_mode">排序</string>
<string name="per_app_proxy_sort_mode_name">依名稱</string>
<string name="per_app_proxy_sort_mode_package_name">依套件名稱</string>
<string name="per_app_proxy_sort_mode_uid">依 UID</string>
<string name="per_app_proxy_sort_mode_install_time">依安裝時間</string>
<string name="per_app_proxy_sort_mode_update_time">依更新時間</string>
<string name="per_app_proxy_sort_mode_reverse">反向排序</string>
<string name="per_app_proxy_filter">篩選</string>
<string name="per_app_proxy_hide_system_apps">隱藏系統應用程式</string>
<string name="per_app_proxy_hide_offline_apps">隱藏離線應用程式</string>
<string name="per_app_proxy_hide_disabled_apps">隱藏停用應用程式</string>
<string name="per_app_proxy_select">選擇</string>
<string name="per_app_proxy_select_all">全選</string>
<string name="per_app_proxy_select_none">全部取消選取</string>
<string name="per_app_proxy_backup">備份</string>
<string name="per_app_proxy_import">從剪貼簿匯入</string>
<string name="per_app_proxy_export">匯出到剪貼簿</string>
<string name="per_app_proxy_scan">掃描</string>
<string name="per_app_proxy_scan_china_apps">中國應用程式</string>
<string name="per_app_proxy_manage">管理</string>
<string name="content_description_app_icon">應用程式圖示</string>
<string name="per_app_proxy_managed_mode">受管理模式</string>
<string name="per_app_proxy_managed_mode_description">自動排除中國應用程式</string>
<string name="per_app_proxy_package_query_mode">模式</string>
<string name="per_app_proxy_shizuku_required">透過 Play 商店安裝時,每個應用程式代理需要 Shizuku。Google Play 拒絕允許我們使用 QUERY_ALL_PACKAGES 權限(同時不禁止其他類似應用程式這樣做),而這是列出應用程式清單所必需的。</string>
<string name="per_app_proxy_root_required">透過 Play 商店安裝時,每個應用程式代理需要 ROOT。Google Play 拒絕允許我們使用 QUERY_ALL_PACKAGES 權限(同時不禁止其他類似應用程式這樣做),而這是列出應用程式清單所必需的。</string>
<string name="privileged_access_required">需要 Root 或 Shizuku 權限來取得完整應用程式清單</string>
<!-- Permissions -->
<string name="background_permission">背景執行權限</string>
<string name="background_permission_description">請授予必要權限,讓 VPN 可以正常運作。\n\n若您使用的是中國廠商裝置,授權後此提示卡片可能仍會顯示。</string>
<string name="request_background_permission">忽略電池最佳化</string>
<string name="location_permission_title">位置權限</string>
<string name="location_permission_description">您的設定檔包含 &lt;strong&gt;&lt;tt&gt;wifi_ssid&lt;/tt&gt;&lt;tt&gt;wifi_bssid&lt;/tt&gt; 路由規則&lt;/strong&gt;。為了使它們正常工作,sing-box 在&lt;strong&gt;背景&lt;/strong&gt;使用 &lt;strong&gt;位置&lt;/strong&gt; 權限來取得已連線 Wi-Fi 網路資訊。這些資訊將&lt;strong&gt;僅用於路由&lt;/strong&gt;</string>
<string name="location_permission_background_description">在 Android 10 及更高版本中,需要&lt;strong&gt;背景位置&lt;/strong&gt;權限。選擇&lt;strong&gt;始終允許&lt;/strong&gt;以授予權限。</string>
<string name="notification_permission_title">通知權限</string>
<string name="notification_permission_required_description">若未授予通知權限,sing-box 無法顯示即時網速。請先授權,或關閉即時網速通知後再啟動服務。</string>
<string name="root_access_required">需要 Root 權限</string>
<string name="root_access_denied">Root 權限被拒絕</string>
<!-- Updates -->
<string name="check_update">檢查更新</string>
<string name="check_update_automatic">自動檢查更新</string>
<string name="check_update_prompt_play">是否啟用從 **Play Store** 自動檢查更新?</string>
<string name="check_update_prompt_github">是否啟用從 **GitHub** 自動檢查更新?</string>
<string name="update_track">更新通道</string>
<string name="update_track_stable">穩定版</string>
<string name="update_track_beta">測試版</string>
<string name="update_track_not_supported">目前通道尚不支援檢查更新</string>
<string name="view_release">查看發布</string>
<string name="downloading">下載中…</string>
<string name="exporting">匯出中…</string>
<string name="no_updates_available">沒有可用的更新</string>
<string name="new_version_available">有新版本可用:%s</string>
<string name="auto_update">自動更新</string>
<string name="auto_update_description">在背景自動下載並安裝更新</string>
<!-- Silent Install -->
<string name="silent_install">靜默安裝</string>
<string name="silent_install_description">無需互動即可安裝更新</string>
<string name="silent_install_method">安裝方式</string>
<string name="silent_install_verify_failed">%s 不可用或權限被拒絕</string>
<string name="install_method_package_installer">PackageInstaller</string>
<string name="install_method_shizuku">Shizuku</string>
<string name="install_method_root">ROOT</string>
<string name="package_installer_not_available">未授予安裝權限</string>
<string name="grant_install_permission">授予安裝權限</string>
<string name="grant_install_permission_description">允許從此來源安裝應用程式</string>
<!-- Shizuku -->
<string name="shizuku_not_available">Shizuku 未安裝或未執行</string>
<string name="shizuku_description">Shizuku 允許應用程式以更高權限直接使用系統 API</string>
<string name="get_shizuku">獲取 Shizuku</string>
<string name="start_shizuku">啟動 Shizuku</string>
<string name="request_shizuku">授權 Shizuku</string>
<!-- Time -->
<string name="time_just_now">剛剛</string>
<string name="time_yesterday">昨天</string>
<string name="time_now">現在</string>
<string name="time_yesterday_short">1天</string>
<string name="time_minutes_short">%d分</string>
<string name="time_hours_short">%d時</string>
<string name="time_days_short">%d天</string>
<plurals name="time_minutes_ago">
<item quantity="one">%d 分鐘前</item>
<item quantity="other">%d 分鐘前</item>
</plurals>
<plurals name="time_hours_ago">
<item quantity="one">%d 小時前</item>
<item quantity="other">%d 小時前</item>
</plurals>
<plurals name="time_days_ago">
<item quantity="one">%d 天前</item>
<item quantity="other">%d 天前</item>
</plurals>
<!-- Menu -->
<string name="menu_undo">撤銷</string>
<string name="menu_redo">重做</string>
<string name="menu_format">格式化</string>
<string name="menu_delete">刪除</string>
<string name="menu_share">分享</string>
<!-- Errors -->
<string name="error_title">錯誤</string>
<string name="error_missing_vpn_permission">缺少 VPN 權限</string>
<string name="error_empty_configuration">設定為空</string>
<string name="error_empty_file">空檔案</string>
<string name="error_decode_profile">解碼設定檔失敗:%s</string>
<string name="error_invalid_configuration">無效的 sing-box 設定:%s</string>
<string name="error_start_command_server">啟動命令伺服器</string>
<string name="error_create_service">建立服務</string>
<string name="error_start_service">啟動服務</string>
<string name="error_deprecated_warning">棄用警告</string>
<string name="error_deprecated_documentation">文件</string>
<string name="file_manager_missing">您的裝置缺少 Android 標準檔案選擇器,請安裝一個,例如 Material Files。</string>
<string name="no_file_manager">未找到檔案管理器</string>
<!-- Failed Operations -->
<string name="failed_save_profile">儲存設定檔失敗:%s</string>
<string name="failed_save_logs">儲存日誌失敗:%s</string>
<string name="failed_share_logs">分享日誌失敗:%s</string>
<string name="failed_read_configuration">讀取設定失敗:%s</string>
<!-- Success Messages -->
<string name="success_profile_saved">設定檔儲存成功</string>
<string name="success_logs_saved">日誌儲存成功</string>
<string name="success_configuration_saved">設定已儲存</string>
<string name="copied_to_clipboard">已複製到剪貼簿</string>
<!-- Toast Messages -->
<string name="toast_clipboard_empty">剪貼簿為空</string>
<string name="toast_copied_to_clipboard">已匯出到剪貼簿</string>
<string name="toast_imported_from_clipboard">已從剪貼簿匯入</string>
<!-- Scanning -->
<string name="profile_add_scan_use_front_camera">前鏡頭</string>
<string name="profile_add_scan_use_vendor_analyzer">使用 MLKit 掃描</string>
<string name="profile_add_scan_enable_torch">手電筒</string>
<string name="message_scanning">掃描中...</string>
<string name="message_scan_app_no_apps_found">找不到符合條件的應用程式</string>
<string name="message_scan_app_found">找到以下應用程式,請選擇您想要的操作。</string>
<!-- Icon Selection -->
<string name="icon">圖示</string>
<string name="icon_count_format">%d 個圖示</string>
<string name="no_icons_found">未找到圖示</string>
<string name="no_icons_match">沒有符合 \"%s\" 的圖示</string>
<string name="profile_icon">設定檔圖示</string>
<string name="select_icon">選擇圖示</string>
<string name="select_profile_icon">選擇設定檔圖示</string>
<string name="search_icons_placeholder">搜尋圖示...</string>
<string name="search_icons">搜尋圖示</string>
<string name="close_search">關閉搜尋</string>
<string name="current_icon_format">當前:%s</string>
<string name="categories">分類</string>
<string name="all_icons">所有圖示</string>
<string name="back_to_categories">回到分類</string>
<!-- QR Code -->
<!-- QR Stream (QRS) -->
<string name="share_as_qrs">分享為 QRS</string>
<string name="qrs_fps">FPS</string>
<string name="qrs_slice_size">分片大小</string>
<string name="qrs_what_is_qrs">什麼是 QRS</string>
<!-- Search -->
<string name="search_placeholder">在文件中尋找</string>
<!-- Content Descriptions (Accessibility) -->
<string name="content_description_back">返回</string>
<string name="content_description_scroll_to_bottom">滾動到底部</string>
<string name="content_description_exit_selection_mode">離開選取模式</string>
<string name="content_description_copy_selected">複製已選取項目</string>
<string name="content_description_clear_search">清除搜尋</string>
<string name="content_description_qr_code">QR 碼</string>
<string name="content_description_resume_logs">繼續顯示日誌</string>
<string name="content_description_pause_logs">暫停日誌</string>
<string name="content_description_collapse_search">收合搜尋</string>
<string name="content_description_search_logs">搜尋日誌</string>
<!-- Xposed Module -->
<string name="xposed_description">sing-box 的特權強化</string>
<!-- Privileged Enhancement -->
<string name="privilege_settings">特權強化</string>
<string name="privilege_module_title">特權模組</string>
<string name="privilege_settings_hide_title">隱藏設定</string>
<string name="privilege_settings_hide_description">為已選取的應用程式啟用 VPN 偵測防護</string>
<string name="privilege_settings_hide_manage">管理</string>
<string name="privilege_settings_hide_test">執行測試</string>
<string name="privilege_settings_hide_test_result">測試結果</string>
<string name="privilege_settings_hide_test_running">正在執行檢測測試…</string>
<string name="privilege_settings_hide_test_not_detected">未檢測到</string>
<string name="privilege_settings_interface_rename_title">介面重新命名</string>
<string name="privilege_settings_interface_prefix">介面前綴</string>
<string name="privilege_settings_vpn_detection_title">VPN 檢測</string>
<string name="privilege_settings_view_logs">檢視日誌</string>
<string name="privilege_settings_export_debug">匯出除錯資訊</string>
<string name="privilege_settings_hook_logs_empty">暫無日誌</string>
<string name="privilege_settings_export_debug_complete">匯出完成</string>
<string name="privilege_settings_export_debug_message">此檔案包含隱私資訊,不應公開分享。大小:%s</string>
<string name="privilege_settings_export_debug_failed">匯出除錯資訊失敗:%s</string>
<string name="privilege_settings_risky_app_title">警告</string>
<string name="privilege_settings_risky_vpn_message_single">此應用程式是 VPN 應用程式,抵抗 VPN 檢測可能造成問題:%1$s</string>
<string name="privilege_settings_risky_vpn_message_multi">這些應用程式是 VPN 應用程式,抵抗 VPN 檢測可能造成問題:%1$s</string>
<string name="privilege_settings_risky_management_message_single">此應用程式具有網路管理權限,抵抗 VPN 檢測可能造成問題:%1$s</string>
<string name="privilege_settings_risky_management_message_multi">這些應用程式具有網路管理權限,抵抗 VPN 檢測可能造成問題:%1$s</string>
<string name="privilege_module_restart_action">重新啟動</string>
<string name="privilege_module_restart_failed">請求重啟失敗:%1$s</string>
<string name="privilege_module_restart_notification_title">需要重啟</string>
<string name="privilege_module_restart_notification_message">LSPosed 模組已更新,請重啟以套用變更。</string>
<string name="privilege_module_restart_channel">LSPosed 模組</string>
<string name="lsposed_module_activated">LSPosed 模組已啟用</string>
<string name="lsposed_module_pending_update">LSPosed 模組待更新</string>
<string name="lsposed_module_pending_downgrade">LSPosed 模組待降級</string>
<string name="lsposed_module_not_activated">LSPosed 模組未啟用</string>
</resources>
@@ -125,7 +125,6 @@
<string name="no_profiles">No profiles configured</string>
<string name="add_profile">Add Profile</string>
<string name="update_profile">Update profile</string>
<string name="share_profile">Share Profile</string>
<string name="import_remote_profile">Import remote profile</string>
<string name="import_remote_profile_message">Are you sure to import remote profile %1$s? You will connect to %2$s to download the configuration.</string>
<string name="import_from_file_description">Import configuration from a local file</string>
@@ -138,7 +137,6 @@
<string name="profile_url" translatable="false">URL</string>
<string name="profile_import_file">Import File</string>
<string name="profile_create">Create</string>
<string name="profile_share">Share</string>
<string name="profile_share_url">Share URL as QR Code</string>
<string name="profile_input_required">Required</string>
<string name="profile_update">Update</string>
@@ -167,7 +165,6 @@
<string name="share_content_json">Share Content JSON File</string>
<string name="unsaved_changes">Unsaved Changes</string>
<string name="unsaved_changes_message">You have unsaved changes. Do you want to discard them?</string>
<string name="profile_qr_code_text">Profile QR Code: %s</string>
<string name="import_profile_confirm_title">Import Profile</string>
<string name="import_profile_confirm_message">Import profile \"%s\"?</string>
<string name="import_action">Import</string>
@@ -276,7 +273,6 @@
<!-- Silent Install -->
<string name="silent_install">Silent Install</string>
<string name="silent_install_title">Silent Install</string>
<string name="silent_install_description">Install updates without interaction</string>
<string name="silent_install_method">Install Method</string>
<string name="silent_install_verify_failed">%s is not available or permission denied</string>
@@ -341,8 +337,6 @@
<string name="failed_save_profile">Failed to save profile: %s</string>
<string name="failed_save_logs">Failed to save logs: %s</string>
<string name="failed_share_logs">Failed to share logs: %s</string>
<string name="failed_save_qr_code">Failed to save QR code: %s</string>
<string name="failed_share_qr_code">Failed to share QR code: %s</string>
<string name="failed_read_configuration">Failed to read configuration: %s</string>
<!-- Success Messages -->
@@ -350,7 +344,6 @@
<string name="success_logs_saved">Logs saved successfully</string>
<string name="success_configuration_saved">Configuration saved</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="qr_code_saved_to_gallery">QR code saved to gallery</string>
<!-- Toast Messages -->
<string name="toast_clipboard_empty">Clipboard is empty</string>
@@ -382,16 +375,10 @@
<string name="back_to_categories">Back to categories</string>
<!-- QR Code -->
<string name="intent_share_qr_code">Share QR Code</string>
<!-- QR Stream (QRS) -->
<string name="share_as_qrs">Share as QR Stream</string>
<string name="qrs_progress">Receiving: %1$d / %2$d blocks</string>
<string name="qrs_speed">Speed</string>
<string name="qrs_interval_ms">Interval: %d ms</string>
<string name="qrs_scanning_mode">QR Stream Mode</string>
<string name="qrs_fps">FPS</string>
<string name="qrs_fps_interval">(%d ms)</string>
<string name="qrs_slice_size">Slice Size</string>
<string name="qrs_what_is_qrs">What is QRS</string>
@@ -415,12 +402,10 @@
<!-- Privileged Enhancement -->
<string name="privilege_module_title">Privilege modules</string>
<string name="lsposed_module">LSPosed Module</string>
<string name="lsposed_module_activated">LSPosed module activated</string>
<string name="lsposed_module_pending_update">LSPosed module update pending</string>
<string name="lsposed_module_pending_downgrade">LSPosed module downgrade pending</string>
<string name="lsposed_module_not_activated">LSPosed module not activated</string>
<string name="lsposed_module_description">System Framework only.</string>
<string name="privilege_settings">Privileged Enhancement</string>
<string name="privilege_settings_hide_title">Hide Settings</string>
<string name="privilege_settings_hide_description">Resist VPN detection for selected apps</string>
@@ -429,8 +414,6 @@
<string name="privilege_settings_hide_test_result">Test Result</string>
<string name="privilege_settings_hide_test_running">Running detection tests…</string>
<string name="privilege_settings_hide_test_not_detected">Not detected</string>
<string name="privilege_settings_test_interfaces">Interfaces:</string>
<string name="privilege_settings_test_http_proxy">HTTP proxy:</string>
<string name="privilege_settings_interface_rename_title">Interface Rename</string>
<string name="privilege_settings_interface_prefix">Interface prefix</string>
<string name="privilege_settings_vpn_detection_title">VPN Detection</string>
@@ -445,12 +428,9 @@
<string name="privilege_settings_risky_vpn_message_multi">These apps are VPN apps. Resisting VPN detection may cause issues: %1$s</string>
<string name="privilege_settings_risky_management_message_single">This app has network management permissions. Resisting VPN detection may cause issues: %1$s</string>
<string name="privilege_settings_risky_management_message_multi">These apps have network management permissions. Resisting VPN detection may cause issues: %1$s</string>
<string name="privilege_module_restart_title">Reboot required</string>
<string name="privilege_module_restart_message">LSPosed module updated. Reboot to apply changes.</string>
<string name="privilege_module_restart_action">Restart</string>
<string name="privilege_module_restart_failed">Failed to request reboot: %1$s</string>
<string name="privilege_module_restart_notification_title">Reboot required</string>
<string name="privilege_module_restart_notification_message">LSPosed module updated. Reboot to apply changes.</string>
<string name="privilege_module_restart_channel">LSPosed Module</string>
<string name="reboot_required">Reboot required</string>
</resources>
+2 -2
View File
@@ -1,5 +1,5 @@
VERSION_CODE=617
VERSION_NAME=1.13.0-rc.2
VERSION_CODE=619
VERSION_NAME=1.13.0-rc.3
GO_VERSION=go1.25.7
@@ -0,0 +1,93 @@
package main
import (
"flag"
"os"
"os/exec"
"path/filepath"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing/common/rw"
)
var target string
func init() {
flag.StringVar(&target, "target", "android", "target platform (android or apple)")
}
func main() {
flag.Parse()
args := []string{
"generate",
"-v",
"--config", "experimental/libbox/ffi.json",
"--platform-type", target,
}
command := exec.Command("sing-ffi", args...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
err := command.Run()
if err != nil {
log.Fatal(err)
}
copyArtifacts(target)
}
func copyArtifacts(target string) {
switch target {
case "android":
copyPath := filepath.Join("..", "sing-box-for-android", "app", "libs")
if rw.IsDir(copyPath) {
copyPath, _ = filepath.Abs(copyPath)
for _, name := range []string{"libbox.aar", "libbox-legacy.aar"} {
artifactPath, found := findArtifactPath(name)
if !found {
continue
}
targetPath := filepath.Join(target, artifactPath)
os.RemoveAll(targetPath)
err := os.Rename(artifactPath, targetPath)
if err != nil {
log.Fatal(err)
}
log.Info("copied ", name, " to ", copyPath)
}
}
case "apple":
copyPath := filepath.Join("..", "sing-box-for-apple")
if rw.IsDir(copyPath) {
sourceDir, found := findArtifactPath("Libbox.xcframework")
if !found {
log.Fatal("Libbox.xcframework not found in current directory or experimental/libbox")
}
targetDir := filepath.Join(copyPath, "Libbox.xcframework")
targetDir, _ = filepath.Abs(targetDir)
err := os.RemoveAll(targetDir)
if err != nil {
log.Fatal(err)
}
err = os.Rename(sourceDir, targetDir)
if err != nil {
log.Fatal(err)
}
log.Info("copied ", sourceDir, " to ", targetDir)
}
}
}
func findArtifactPath(name string) (string, bool) {
candidates := []string{
name,
filepath.Join("experimental", "libbox", name),
}
for _, candidate := range candidates {
if rw.IsFile(candidate) || rw.IsDir(candidate) {
return candidate, true
}
}
return "", false
}
+2
View File
@@ -9,6 +9,7 @@ import (
"github.com/sagernet/sing-box/common/urltest"
"github.com/sagernet/sing-box/experimental/deprecated"
"github.com/sagernet/sing-box/include"
"github.com/sagernet/sing-box/log"
"github.com/sagernet/sing-box/option"
E "github.com/sagernet/sing/common/exceptions"
"github.com/sagernet/sing/common/json"
@@ -103,6 +104,7 @@ func (s *StartedService) newInstance(profileContent string, overrideOptions *Ove
i.clashServer = service.FromContext[adapter.ClashServer](ctx)
i.pauseManager = service.FromContext[pause.Manager](ctx)
i.cacheFile = service.FromContext[adapter.CacheFile](ctx)
log.SetStdLogger(boxInstance.LogFactory().Logger())
return i, nil
}
+9 -1
View File
@@ -2,7 +2,7 @@
icon: material/alert-decagram
---
#### 1.13.0-rc.2
#### 1.13.0-rc.3
* Fixes and improvements
@@ -169,6 +169,14 @@ Also, documentation has been updated with a warning about uTLS fingerprinting vu
uTLS is not recommended for censorship circumvention due to fundamental architectural limitations;
use NaiveProxy instead for TLS fingerprint resistance.
#### 1.12.21
* Fixes and improvements
#### 1.13.0-rc.2
* Fixes and improvements
#### 1.12.20
* Fixes and improvements
@@ -362,7 +362,7 @@ func (c *CommandClient) handleStatusStream() {
c.handler.Disconnected(err.Error())
return
}
c.handler.WriteStatus(StatusMessageFromGRPC(status))
c.handler.WriteStatus(statusMessageFromGRPC(status))
}
}
@@ -381,7 +381,7 @@ func (c *CommandClient) handleGroupStream() {
c.handler.Disconnected(err.Error())
return
}
c.handler.WriteGroups(OutboundGroupIteratorFromGRPC(groups))
c.handler.WriteGroups(outboundGroupIteratorFromGRPC(groups))
}
}
@@ -447,7 +447,7 @@ func (c *CommandClient) handleConnectionsStream() {
c.handler.Disconnected(err.Error())
return
}
libboxEvents := ConnectionEventsFromGRPC(events)
libboxEvents := connectionEventsFromGRPC(events)
c.handler.WriteConnectionEvents(libboxEvents)
}
}
@@ -523,7 +523,7 @@ func (c *CommandClient) GetSystemProxyStatus() (*SystemProxyStatus, error) {
if err != nil {
return nil, err
}
return SystemProxyStatusFromGRPC(status), nil
return systemProxyStatusFromGRPC(status), nil
})
}
@@ -43,7 +43,7 @@ type CommandServerHandler interface {
}
func NewCommandServer(handler CommandServerHandler, platformInterface PlatformInterface) (*CommandServer, error) {
ctx := BaseContext(platformInterface)
ctx := baseContext(platformInterface)
platformWrapper := &platformInterfaceWrapper{
iif: platformInterface,
useProcFS: platformInterface.UseProcFS(),
+15 -15
View File
@@ -32,11 +32,11 @@ type OutboundGroup struct {
Selectable bool
Selected string
IsExpand bool
ItemList []*OutboundGroupItem
itemList []*OutboundGroupItem
}
func (g *OutboundGroup) GetItems() OutboundGroupItemIterator {
return newIterator(g.ItemList)
return newIterator(g.itemList)
}
type OutboundGroupIterator interface {
@@ -267,12 +267,12 @@ type Connection struct {
Rule string
Outbound string
OutboundType string
ChainList []string
chainList []string
ProcessInfo *ProcessInfo
}
func (c *Connection) Chain() StringIterator {
return newIterator(c.ChainList)
return newIterator(c.chainList)
}
func (c *Connection) DisplayDestination() string {
@@ -292,7 +292,7 @@ type ConnectionIterator interface {
HasNext() bool
}
func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage {
func statusMessageFromGRPC(status *daemon.Status) *StatusMessage {
if status == nil {
return nil
}
@@ -309,7 +309,7 @@ func StatusMessageFromGRPC(status *daemon.Status) *StatusMessage {
}
}
func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {
func outboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator {
if groups == nil || len(groups.Group) == 0 {
return newIterator([]*OutboundGroup{})
}
@@ -323,7 +323,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator
IsExpand: g.IsExpand,
}
for _, item := range g.Items {
libboxGroup.ItemList = append(libboxGroup.ItemList, &OutboundGroupItem{
libboxGroup.itemList = append(libboxGroup.itemList, &OutboundGroupItem{
Tag: item.Tag,
Type: item.Type,
URLTestTime: item.UrlTestTime,
@@ -335,7 +335,7 @@ func OutboundGroupIteratorFromGRPC(groups *daemon.Groups) OutboundGroupIterator
return newIterator(libboxGroups)
}
func ConnectionFromGRPC(conn *daemon.Connection) Connection {
func connectionFromGRPC(conn *daemon.Connection) Connection {
var processInfo *ProcessInfo
if conn.ProcessInfo != nil {
processInfo = &ProcessInfo{
@@ -367,12 +367,12 @@ func ConnectionFromGRPC(conn *daemon.Connection) Connection {
Rule: conn.Rule,
Outbound: conn.Outbound,
OutboundType: conn.OutboundType,
ChainList: conn.ChainList,
chainList: conn.ChainList,
ProcessInfo: processInfo,
}
}
func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
func connectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
if event == nil {
return nil
}
@@ -384,13 +384,13 @@ func ConnectionEventFromGRPC(event *daemon.ConnectionEvent) *ConnectionEvent {
ClosedAt: event.ClosedAt,
}
if event.Connection != nil {
conn := ConnectionFromGRPC(event.Connection)
conn := connectionFromGRPC(event.Connection)
libboxEvent.Connection = &conn
}
return libboxEvent
}
func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents {
func connectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents {
if events == nil {
return nil
}
@@ -398,14 +398,14 @@ func ConnectionEventsFromGRPC(events *daemon.ConnectionEvents) *ConnectionEvents
Reset: events.Reset_,
}
for _, event := range events.Events {
if libboxEvent := ConnectionEventFromGRPC(event); libboxEvent != nil {
if libboxEvent := connectionEventFromGRPC(event); libboxEvent != nil {
libboxEvents.events = append(libboxEvents.events, libboxEvent)
}
}
return libboxEvents
}
func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {
func systemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxyStatus {
if status == nil {
return nil
}
@@ -415,7 +415,7 @@ func SystemProxyStatusFromGRPC(status *daemon.SystemProxyStatus) *SystemProxySta
}
}
func SystemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
func systemProxyStatusToGRPC(status *SystemProxyStatus) *daemon.SystemProxyStatus {
if status == nil {
return nil
}
+3 -3
View File
@@ -22,7 +22,7 @@ import (
"github.com/sagernet/sing/service/filemanager"
)
func BaseContext(platformInterface PlatformInterface) context.Context {
func baseContext(platformInterface PlatformInterface) context.Context {
dnsRegistry := include.DNSTransportRegistry()
if platformInterface != nil {
if localTransport := platformInterface.LocalDNSTransport(); localTransport != nil {
@@ -45,7 +45,7 @@ func parseConfig(ctx context.Context, configContent string) (option.Options, err
}
func CheckConfig(configContent string) error {
ctx := BaseContext(nil)
ctx := baseContext(nil)
options, err := parseConfig(ctx, configContent)
if err != nil {
return err
@@ -189,7 +189,7 @@ func (s *interfaceMonitorStub) MyInterface() string {
}
func FormatConfig(configContent string) (*StringBox, error) {
options, err := parseConfig(BaseContext(nil), configContent)
options, err := parseConfig(baseContext(nil), configContent)
if err != nil {
return nil, err
}
+167
View File
@@ -0,0 +1,167 @@
{
"version": 1,
"packages": [
{
"id": "libbox",
"path": ".",
"java_package": "io.nekohasekai.libbox",
"apple_prefix": "Libbox"
}
],
"builds": [
{
"id": "android-main",
"packages": ["libbox"],
"default": {
"tags": [
"with_gvisor",
"with_quic",
"with_wireguard",
"with_utls",
"with_naive_outbound",
"with_clash_api",
"with_conntrack",
"badlinkname",
"tfogo_checklinkname0",
"with_tailscale",
"ts_omit_logtail",
"ts_omit_ssh",
"ts_omit_drive",
"ts_omit_taildrop",
"ts_omit_webclient",
"ts_omit_doctor",
"ts_omit_capture",
"ts_omit_kube",
"ts_omit_aws",
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
}
},
{
"id": "android-legacy",
"packages": ["libbox"],
"default": {
"tags": [
"with_gvisor",
"with_quic",
"with_wireguard",
"with_utls",
"with_clash_api",
"with_conntrack",
"badlinkname",
"tfogo_checklinkname0",
"with_tailscale",
"ts_omit_logtail",
"ts_omit_ssh",
"ts_omit_drive",
"ts_omit_taildrop",
"ts_omit_webclient",
"ts_omit_doctor",
"ts_omit_capture",
"ts_omit_kube",
"ts_omit_aws",
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
}
},
{
"id": "apple",
"packages": ["libbox"],
"default": {
"tags": [
"with_gvisor",
"with_quic",
"with_wireguard",
"with_utls",
"with_naive_outbound",
"with_clash_api",
"with_conntrack",
"badlinkname",
"tfogo_checklinkname0",
"with_dhcp",
"grpcnotrace",
"with_tailscale",
"ts_omit_logtail",
"ts_omit_ssh",
"ts_omit_drive",
"ts_omit_taildrop",
"ts_omit_webclient",
"ts_omit_doctor",
"ts_omit_capture",
"ts_omit_kube",
"ts_omit_aws",
"ts_omit_synology",
"ts_omit_bird"
],
"ldflags": "-X github.com/sagernet/sing-box/constant.Version=$(CGO_ENABLED=0 go run github.com/sagernet/sing-box/cmd/internal/read_tag@latest) -X internal/godebug.defaultGODEBUG=multipathtcp=0 -s -w -buildid= -checklinkname=0",
"trimpath": true
},
"overrides": [
{
"match": { "os": "ios" },
"tags_append": ["with_low_memory"]
},
{
"match": { "os": "tvos" },
"tags_append": ["with_low_memory"]
}
]
}
],
"platforms": [
{
"type": "android",
"build": "android-main",
"min_sdk": 23,
"lib_name": "box",
"languages": [{ "type": "java" }],
"artifacts": [
{
"type": "aar",
"output_path": "libbox.aar"
}
]
},
{
"type": "android",
"build": "android-legacy",
"min_sdk": 21,
"lib_name": "box",
"languages": [{ "type": "java" }],
"artifacts": [
{
"type": "aar",
"output_path": "libbox-legacy.aar"
}
]
},
{
"type": "apple",
"build": "apple",
"targets": [
"ios/arm64",
"ios/simulator/arm64",
"ios/simulator/amd64",
"tvos/arm64",
"tvos/simulator/arm64",
"tvos/simulator/amd64",
"macos/arm64",
"macos/amd64"
],
"languages": [{ "type": "objc" }],
"artifacts": [
{
"type": "xcframework",
"module_name": "Libbox",
"output_path": "Libbox.xcframework"
}
]
}
]
}

Some files were not shown because too many files have changed in this diff Show More