diff --git a/.github/update.log b/.github/update.log index e3a805ade1..c484a31613 100644 --- a/.github/update.log +++ b/.github/update.log @@ -1196,3 +1196,4 @@ Update On Tue Nov 25 19:40:43 CET 2025 Update On Wed Nov 26 19:35:47 CET 2025 Update On Thu Nov 27 19:38:17 CET 2025 Update On Fri Nov 28 19:37:50 CET 2025 +Update On Sat Nov 29 19:37:01 CET 2025 diff --git a/brook/ping/ping.json b/brook/ping/ping.json index 7c43db6cf1..f218e6b647 100644 --- a/brook/ping/ping.json +++ b/brook/ping/ping.json @@ -1,7 +1,7 @@ { "version": "20250808", - "text": "Brook Business: Powering Your Own Branded Client", - "link": "https://www.txthinking.com/talks/articles/brook-business-en.article", - "text_zh": "Brook Business: 让你拥有自己品牌的 Brook 客户端", - "link_zh": "https://www.txthinking.com/talks/articles/brook-business.article" + "text": "Pre-release reminder: Brook v20260101, released on January 1, 2026, will reset the local cache. Please back up the brook link and script beforehand.", + "link": "https://www.txthinking.com/talks/articles/brook-v20260101-en.article", + "text_zh": "预发布提醒:在 2026-01-01 发布的 Brook v20260101 将会重置本地缓存。请提前备份 brook link 和 script。", + "link_zh": "https://www.txthinking.com/talks/articles/brook-v20260101.article" } diff --git a/clash-meta-android/app/build.gradle.kts b/clash-meta-android/app/build.gradle.kts index 3bd47e00a8..5272b86afb 100644 --- a/clash-meta-android/app/build.gradle.kts +++ b/clash-meta-android/app/build.gradle.kts @@ -24,6 +24,8 @@ dependencies { implementation(libs.androidx.coordinator) implementation(libs.androidx.recyclerview) implementation(libs.google.material) + implementation(libs.quickie.bundled) + implementation(libs.androidx.activity.ktx) } tasks.getByName("clean", type = Delete::class) { diff --git a/clash-meta-android/app/src/main/java/com/github/kr328/clash/NewProfileActivity.kt b/clash-meta-android/app/src/main/java/com/github/kr328/clash/NewProfileActivity.kt index 529c146f90..99af8d5c76 100644 --- a/clash-meta-android/app/src/main/java/com/github/kr328/clash/NewProfileActivity.kt +++ b/clash-meta-android/app/src/main/java/com/github/kr328/clash/NewProfileActivity.kt @@ -6,24 +6,35 @@ import android.content.Intent import android.net.Uri import android.provider.Settings import androidx.activity.result.contract.ActivityResultContracts +import androidx.lifecycle.lifecycleScope import com.github.kr328.clash.common.constants.Intents import com.github.kr328.clash.common.util.intent import com.github.kr328.clash.common.util.setUUID import com.github.kr328.clash.design.NewProfileDesign +import com.github.kr328.clash.design.R import com.github.kr328.clash.design.model.ProfileProvider +import com.github.kr328.clash.design.util.showExceptionToast import com.github.kr328.clash.service.model.Profile import com.github.kr328.clash.util.withProfile +import io.github.g00fy2.quickie.QRResult +import io.github.g00fy2.quickie.QRResult.QRError +import io.github.g00fy2.quickie.QRResult.QRMissingPermission +import io.github.g00fy2.quickie.QRResult.QRSuccess +import io.github.g00fy2.quickie.QRResult.QRUserCanceled +import io.github.g00fy2.quickie.ScanQRCode import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch import kotlinx.coroutines.selects.select import kotlinx.coroutines.withContext import java.util.* -import com.github.kr328.clash.design.R class NewProfileActivity : BaseActivity() { private val self: NewProfileActivity get() = this + private val scanLauncher = registerForActivityResult(ScanQRCode(), ::scanResultHandler) + override suspend fun main() { val design = NewProfileDesign(this) @@ -45,8 +56,14 @@ class NewProfileActivity : BaseActivity() { val uuid: UUID? = when (val p = it.provider) { is ProfileProvider.File -> create(Profile.Type.File, name) + is ProfileProvider.Url -> create(Profile.Type.Url, name) + + is ProfileProvider.QR -> { + null + } + is ProfileProvider.External -> { val data = p.get() @@ -68,9 +85,14 @@ class NewProfileActivity : BaseActivity() { launchProperties(uuid) } } + is NewProfileDesign.Request.OpenDetail -> { launchAppDetailed(it.provider) } + + is NewProfileDesign.Request.LaunchScanner -> { + scanLauncher.launch(null) + } } } } @@ -138,7 +160,41 @@ class NewProfileActivity : BaseActivity() { ProfileProvider.External(name.toString(), summary.toString(), icon, intent) } - listOf(ProfileProvider.File(self), ProfileProvider.Url(self)) + providers + listOf( + ProfileProvider.File(self), + ProfileProvider.Url(self), + ProfileProvider.QR(self) + ) + providers } } + + private fun scanResultHandler(result: QRResult) { + lifecycleScope.launch { + when (result) { + is QRSuccess -> { + val url = result.content.rawValue + ?: result.content.rawBytes?.let { String(it) }.orEmpty() + + createProfileByQrCode(url) + } + + QRUserCanceled -> {} + QRMissingPermission -> design?.showExceptionToast(getString(R.string.import_from_qr_no_permission)) + is QRError -> design?.showExceptionToast(getString(R.string.import_from_qr_exception)) + } + } + } + + private suspend fun createProfileByQrCode(url: String) { + withProfile { + launchProperties( + create( + type = Profile.Type.Url, + name = getString(R.string.new_profile), + url, + ) + ) + } + } + } diff --git a/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/NewProfileDesign.kt b/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/NewProfileDesign.kt index e38d01e790..5526251186 100644 --- a/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/NewProfileDesign.kt +++ b/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/NewProfileDesign.kt @@ -11,6 +11,7 @@ class NewProfileDesign(context: Context) : Design(cont sealed class Request { data class Create(val provider: ProfileProvider) : Request() data class OpenDetail(val provider: ProfileProvider.External) : Request() + data class LaunchScanner(val provider: ProfileProvider.QR) : Request() } private val binding = DesignNewProfileBinding @@ -38,7 +39,12 @@ class NewProfileDesign(context: Context) : Design(cont } private fun requestCreate(provider: ProfileProvider) { - requests.trySend(Request.Create(provider)) + if (provider is ProfileProvider.QR) { + requests.trySend(Request.LaunchScanner(provider)) + } else { + requests.trySend(Request.Create(provider)) + } + } private fun requestDetail(provider: ProfileProvider): Boolean { diff --git a/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt b/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt index 7b09c22bd8..0bb9f4a366 100644 --- a/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt +++ b/clash-meta-android/design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt @@ -14,6 +14,8 @@ sealed class ProfileProvider { get() = context.getString(R.string.import_from_file) override val icon: Drawable? get() = context.getDrawableCompat(R.drawable.ic_baseline_attach_file) + + } class Url(private val context: Context) : ProfileProvider() { @@ -25,6 +27,14 @@ sealed class ProfileProvider { get() = context.getDrawableCompat(R.drawable.ic_baseline_cloud_download) } + class QR(private val context: Context) : ProfileProvider() { + override val name: String + get() = context.getString(R.string.qr) + override val summary: String + get() = context.getString(R.string.import_from_qr) + override val icon: Drawable? + get() = context.getDrawableCompat(R.drawable.baseline_qr_code_scanner) + } class External( override val name: String, override val summary: String, diff --git a/clash-meta-android/design/src/main/res/drawable/baseline_qr_code_scanner.xml b/clash-meta-android/design/src/main/res/drawable/baseline_qr_code_scanner.xml new file mode 100644 index 0000000000..0c3341938a --- /dev/null +++ b/clash-meta-android/design/src/main/res/drawable/baseline_qr_code_scanner.xml @@ -0,0 +1,9 @@ + + + + diff --git a/clash-meta-android/design/src/main/res/values-ja-rJP/strings.xml b/clash-meta-android/design/src/main/res/values-ja-rJP/strings.xml index 102066fb1c..c346e3e760 100644 --- a/clash-meta-android/design/src/main/res/values-ja-rJP/strings.xml +++ b/clash-meta-android/design/src/main/res/values-ja-rJP/strings.xml @@ -30,6 +30,7 @@ ファイルからインポート URL URLからインポート + QRコードからインポート 外部入力 %s (未保存) アプリが破損しています @@ -250,4 +251,6 @@ Force DNS Mapping Parse Pure IP Override Destination + カメラのアクセスが制限されています。設定から有効にしてください。 + システムで予期しない例外が発生しました。 \ No newline at end of file diff --git a/clash-meta-android/design/src/main/res/values-ko-rKR/strings.xml b/clash-meta-android/design/src/main/res/values-ko-rKR/strings.xml index dc852797f9..ab0bbf0a5e 100644 --- a/clash-meta-android/design/src/main/res/values-ko-rKR/strings.xml +++ b/clash-meta-android/design/src/main/res/values-ko-rKR/strings.xml @@ -31,6 +31,7 @@ URL URL에서 가져오기 외부 + QR코드에서 가져오기 %s (저장되지 않음) 앱 오류 앱 중지 @@ -250,4 +251,6 @@ Force DNS Mapping Parse Pure IP Override Destination + 카메라 접근이 제한되었습니다. 설정에서 허용해 주세요. + 처리되지 않은 시스템 예외가 발생했습니다. \ No newline at end of file diff --git a/clash-meta-android/design/src/main/res/values-ru/strings.xml b/clash-meta-android/design/src/main/res/values-ru/strings.xml index 753a0f1d00..342b639d28 100644 --- a/clash-meta-android/design/src/main/res/values-ru/strings.xml +++ b/clash-meta-android/design/src/main/res/values-ru/strings.xml @@ -38,6 +38,7 @@ URL Импорт из URL Внешний + Импорт из QR-кода %s (не сохранён) Приложение сломано @@ -314,4 +315,6 @@ Force DNS Mapping Parse Pure IP Override Destination + Доступ к камере ограничен. Разрешите его в настройках. + Произошла не обрабатываемая системная ошибка. diff --git a/clash-meta-android/design/src/main/res/values-vi/strings.xml b/clash-meta-android/design/src/main/res/values-vi/strings.xml index ea361405b0..2bf8aacad4 100644 --- a/clash-meta-android/design/src/main/res/values-vi/strings.xml +++ b/clash-meta-android/design/src/main/res/values-vi/strings.xml @@ -236,4 +236,7 @@ Các cài đặt đã được đặt lại và các cấu hình cũ cần được lưu lại. Tuỳ chọn VpnService Cảnh báo + Nhập từ Mã QR + Quyền truy cập camera bị hạn chế. Vui lòng bật trong Cài đặt. + Đã xảy ra ngoại lệ hệ thống không xử lý được. diff --git a/clash-meta-android/design/src/main/res/values-zh-rHK/strings.xml b/clash-meta-android/design/src/main/res/values-zh-rHK/strings.xml index 51d62c1318..537b33f3ee 100644 --- a/clash-meta-android/design/src/main/res/values-zh-rHK/strings.xml +++ b/clash-meta-android/design/src/main/res/values-zh-rHK/strings.xml @@ -41,6 +41,7 @@ 從文件導入 從 URL 導入 界面 + 從二維碼導入 無效的 URL Logcat 日誌 @@ -247,4 +248,6 @@ Force DNS Mapping Parse Pure IP Override Destination + 相機權限受限,請前往設定開啟。 + 發生系統未知異常,操作失敗。 \ No newline at end of file diff --git a/clash-meta-android/design/src/main/res/values-zh-rTW/strings.xml b/clash-meta-android/design/src/main/res/values-zh-rTW/strings.xml index 91c69fa7be..cf70cc2d41 100644 --- a/clash-meta-android/design/src/main/res/values-zh-rTW/strings.xml +++ b/clash-meta-android/design/src/main/res/values-zh-rTW/strings.xml @@ -40,6 +40,7 @@ 歷史 從檔案匯入 從 URL 匯入 + 從二維碼導入 介面 無效 URL Logcat @@ -247,4 +248,6 @@ Force DNS Mapping Parse Pure IP Override Destination + 相機權限受限,請前往設定開啟。 + 發生系統未知異常,操作失敗。 diff --git a/clash-meta-android/design/src/main/res/values-zh/strings.xml b/clash-meta-android/design/src/main/res/values-zh/strings.xml index d1eb2b75b4..e8145bf64b 100644 --- a/clash-meta-android/design/src/main/res/values-zh/strings.xml +++ b/clash-meta-android/design/src/main/res/values-zh/strings.xml @@ -42,6 +42,7 @@ 历史 从文件导入 从 URL 导入 + 从二维码导入 界面 无效的 URL Logcat @@ -257,4 +258,6 @@ 外部控制 Clash.Meta 服务已启动 Clash.Meta 服务已停止 + 相机权限受限,请前往设置开启。 + 发生系统未知异常,操作失败。 diff --git a/clash-meta-android/design/src/main/res/values/strings.xml b/clash-meta-android/design/src/main/res/values/strings.xml index 9681f9f732..786131255c 100644 --- a/clash-meta-android/design/src/main/res/values/strings.xml +++ b/clash-meta-android/design/src/main/res/values/strings.xml @@ -41,7 +41,9 @@ File Import from File URL + QR Code Import from URL + Import from QR Code External %s (Unsaved) @@ -344,4 +346,6 @@ Hide App Icon You can dial *#*#252746382#*#* to open this App + Camera access is restricted. Please enable it in Settings. + An unhandled system exception occurred. diff --git a/clash-meta-android/gradle/libs.versions.toml b/clash-meta-android/gradle/libs.versions.toml index 5f8abde456..3190850eff 100644 --- a/clash-meta-android/gradle/libs.versions.toml +++ b/clash-meta-android/gradle/libs.versions.toml @@ -16,6 +16,8 @@ serialization = "1.3.3" kaidl = "1.15" room = "2.4.2" multiprocess = "1.0.0" +quickie = "1.11.0" +androidx-activity-ktx = "1.9.0" [libraries] build-android = { module = "com.android.tools.build:gradle", version.ref = "agp" } @@ -39,5 +41,7 @@ google-material = { module = "com.google.android.material:material", version.ref kaidl-compiler = { module = "com.github.kr328.kaidl:kaidl", version.ref = "kaidl" } kaidl-runtime = { module = "com.github.kr328.kaidl:kaidl-runtime", version.ref = "kaidl" } rikkax-multiprocess = { module = "dev.rikka.rikkax.preference:multiprocess", version.ref = "multiprocess" } +quickie-bundled = { group = "io.github.g00fy2.quickie", name = "quickie-bundled", version.ref = "quickie" } +androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidx-activity-ktx" } [plugins] diff --git a/clash-meta/adapter/outbound/sudoku.go b/clash-meta/adapter/outbound/sudoku.go index 1db9ad2dc5..9b4a9c9db1 100644 --- a/clash-meta/adapter/outbound/sudoku.go +++ b/clash-meta/adapter/outbound/sudoku.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/metacubex/mihomo/log" + "github.com/saba-futai/sudoku/apis" "github.com/saba-futai/sudoku/pkg/crypto" "github.com/saba-futai/sudoku/pkg/obfs/httpmask" @@ -38,8 +40,8 @@ type SudokuOption struct { AEADMethod string `proxy:"aead-method,omitempty"` PaddingMin *int `proxy:"padding-min,omitempty"` PaddingMax *int `proxy:"padding-max,omitempty"` - Seed string `proxy:"seed,omitempty"` TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HTTPMask bool `proxy:"http-mask,omitempty"` } // DialContext implements C.ProxyAdapter @@ -120,8 +122,10 @@ func (s *Sudoku) buildConfig(metadata *C.Metadata) (*apis.ProtocolConfig, error) } func (s *Sudoku) streamConn(rawConn net.Conn, cfg *apis.ProtocolConfig) (_ net.Conn, err error) { - if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { - return nil, fmt.Errorf("write http mask failed: %w", err) + if !cfg.DisableHTTPMask { + if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { + return nil, fmt.Errorf("write http mask failed: %w", err) + } } obfsConn := sudoku.NewConn(rawConn, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false) @@ -163,12 +167,14 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { return nil, fmt.Errorf("table-type must be prefer_ascii or prefer_entropy") } - seed := option.Seed - if seed == "" { - seed = option.Key + seed := option.Key + if recoveredFromKey, err := crypto.RecoverPublicKey(option.Key); err == nil { + seed = crypto.EncodePoint(recoveredFromKey) } + start := time.Now() table := sudoku.NewTable(seed, tableType) + log.Infoln("[Sudoku] Tables initialized (%s) in %v", tableType, time.Since(start)) defaultConf := apis.DefaultConfig() paddingMin := defaultConf.PaddingMin @@ -194,6 +200,7 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { PaddingMin: paddingMin, PaddingMax: paddingMax, HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, + DisableHTTPMask: !option.HTTPMask, } if option.AEADMethod != "" { baseConf.AEADMethod = option.AEADMethod diff --git a/clash-meta/adapter/outboundgroup/parser.go b/clash-meta/adapter/outboundgroup/parser.go index 2dcdd628b2..4640685594 100644 --- a/clash-meta/adapter/outboundgroup/parser.go +++ b/clash-meta/adapter/outboundgroup/parser.go @@ -186,7 +186,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide strategy := parseStrategy(config) return NewLoadBalance(groupOption, providers, strategy) case "relay": - group = NewRelay(groupOption, providers) + return nil, fmt.Errorf("%w: The group [%s] with relay type was removed, please using dialer-proxy instead", errType, groupName) default: return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) } diff --git a/clash-meta/docs/config.yaml b/clash-meta/docs/config.yaml index 0d5fb75e22..bef2c65914 100644 --- a/clash-meta/docs/config.yaml +++ b/clash-meta/docs/config.yaml @@ -1047,8 +1047,8 @@ proxies: # socks5 aead-method: chacha20-poly1305 # 可选值:chacha20-poly1305、aes-128-gcm、none 我们保证在none的情况下sudoku混淆层仍然确保安全 padding-min: 2 # 最小填充字节数 padding-max: 7 # 最大填充字节数 - seed: "" # 如果使用sudoku生成的ED25519密钥对,请填写密钥对中的公钥(如果你有安全焦虑,填入私钥也可以,只是私钥长度比较长不好看而已),否则填入和服务端相同的uuid table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + http-mask: true # 是否启用http掩码 # anytls - name: anytls @@ -1587,7 +1587,7 @@ listeners: aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及none,sudoku的混淆层可以确保none情况下数据安全 padding-min: 1 # 填充最小长度 padding-max: 15 # 填充最大长度,均不建议过大 - seed: "" # 如果你不使用ED25519密钥对,就请填入客户端的key,否则仍然是公钥 + seed: "" # 如果你不使用ED25519密钥对,就请填入uuid,否则仍然是公钥 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 handshake-timeout: 5 # optional diff --git a/clash-meta/go.mod b/clash-meta/go.mod index d7e40c7eae..aaf8890ccf 100644 --- a/clash-meta/go.mod +++ b/clash-meta/go.mod @@ -6,7 +6,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.5 - github.com/enfein/mieru/v3 v3.24.0 + github.com/enfein/mieru/v3 v3.24.1 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 @@ -43,7 +43,7 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 - github.com/saba-futai/sudoku v0.0.1-e + github.com/saba-futai/sudoku v0.0.1-g github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 diff --git a/clash-meta/go.sum b/clash-meta/go.sum index 15ba6b90eb..1186bda8f2 100644 --- a/clash-meta/go.sum +++ b/clash-meta/go.sum @@ -25,8 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/enfein/mieru/v3 v3.24.0 h1:UpS6fTj242wAz2Xa/ieavMN8owcWdPzLFB11UqYs5GY= -github.com/enfein/mieru/v3 v3.24.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.24.1 h1:iX9py3+GExxJxLaxjHAEmQmoE1r0y2hDIsliija+jTI= +github.com/enfein/mieru/v3 v3.24.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/saba-futai/sudoku v0.0.1-e h1:PetJcOdoybBWGT1k65puNv+kt6Cmger6i/TSfuu6CdM= -github.com/saba-futai/sudoku v0.0.1-e/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= +github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE= +github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/clash-nyanpasu/manifest/version.json b/clash-nyanpasu/manifest/version.json index 4f885c00fb..c50c4e9f32 100644 --- a/clash-nyanpasu/manifest/version.json +++ b/clash-nyanpasu/manifest/version.json @@ -2,7 +2,7 @@ "manifest_version": 1, "latest": { "mihomo": "v1.19.16", - "mihomo_alpha": "alpha-8b6ba22", + "mihomo_alpha": "alpha-6cf1743", "clash_rs": "v0.9.2", "clash_premium": "2023-09-05-gdcc8d87", "clash_rs_alpha": "0.9.2-alpha+sha.87c7b2c" @@ -69,5 +69,5 @@ "linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf" } }, - "updated_at": "2025-11-26T22:21:25.971Z" + "updated_at": "2025-11-28T22:21:05.844Z" } diff --git a/filebrowser/frontend/pnpm-lock.yaml b/filebrowser/frontend/pnpm-lock.yaml index 594a5dbac5..670e35fe9c 100644 --- a/filebrowser/frontend/pnpm-lock.yaml +++ b/filebrowser/frontend/pnpm-lock.yaml @@ -119,7 +119,7 @@ importers: version: 6.0.2(vite@7.2.4(@types/node@24.10.1)(terser@5.44.1)(yaml@2.7.0))(vue@3.5.25(typescript@5.9.3)) '@vue/eslint-config-prettier': specifier: ^10.2.0 - version: 10.2.0(eslint@9.39.1)(prettier@3.7.1) + version: 10.2.0(eslint@9.39.1)(prettier@3.7.2) '@vue/eslint-config-typescript': specifier: ^14.6.0 version: 14.6.0(eslint-plugin-vue@10.6.2(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.2.0(eslint@9.39.1)))(eslint@9.39.1)(typescript@5.9.3) @@ -137,7 +137,7 @@ importers: version: 10.1.8(eslint@9.39.1) eslint-plugin-prettier: specifier: ^5.5.1 - version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.1) + version: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2) eslint-plugin-vue: specifier: ^10.5.1 version: 10.6.2(@typescript-eslint/parser@8.37.0(eslint@9.39.1)(typescript@5.9.3))(eslint@9.39.1)(vue-eslint-parser@10.2.0(eslint@9.39.1)) @@ -146,7 +146,7 @@ importers: version: 8.5.6 prettier: specifier: ^3.6.2 - version: 3.7.1 + version: 3.7.2 terser: specifier: ^5.43.1 version: 5.44.1 @@ -2167,8 +2167,8 @@ packages: resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} engines: {node: '>=6.0.0'} - prettier@3.7.1: - resolution: {integrity: sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==} + prettier@3.7.2: + resolution: {integrity: sha512-n3HV2J6QhItCXndGa3oMWvWFAgN1ibnS7R9mt6iokScBOC0Ul9/iZORmU2IWUMcyAQaMPjTlY3uT34TqocUxMA==} engines: {node: '>=14'} hasBin: true @@ -3882,12 +3882,12 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/eslint-config-prettier@10.2.0(eslint@9.39.1)(prettier@3.7.1)': + '@vue/eslint-config-prettier@10.2.0(eslint@9.39.1)(prettier@3.7.2)': dependencies: eslint: 9.39.1 eslint-config-prettier: 10.1.8(eslint@9.39.1) - eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.1) - prettier: 3.7.1 + eslint-plugin-prettier: 5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2) + prettier: 3.7.2 transitivePeerDependencies: - '@types/eslint' @@ -4219,10 +4219,10 @@ snapshots: dependencies: eslint: 9.39.1 - eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.1): + eslint-plugin-prettier@5.5.4(eslint-config-prettier@10.1.8(eslint@9.39.1))(eslint@9.39.1)(prettier@3.7.2): dependencies: eslint: 9.39.1 - prettier: 3.7.1 + prettier: 3.7.2 prettier-linter-helpers: 1.0.0 synckit: 0.11.11 optionalDependencies: @@ -4697,7 +4697,7 @@ snapshots: dependencies: fast-diff: 1.3.0 - prettier@3.7.1: {} + prettier@3.7.2: {} pretty-bytes@7.1.0: {} diff --git a/filebrowser/frontend/src/components/files/CsvViewer.vue b/filebrowser/frontend/src/components/files/CsvViewer.vue new file mode 100644 index 0000000000..ad1e7e229f --- /dev/null +++ b/filebrowser/frontend/src/components/files/CsvViewer.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/filebrowser/frontend/src/i18n/en.json b/filebrowser/frontend/src/i18n/en.json index d729944417..869991fa7e 100644 --- a/filebrowser/frontend/src/i18n/en.json +++ b/filebrowser/frontend/src/i18n/en.json @@ -43,7 +43,8 @@ "upload": "Upload", "openFile": "Open file", "discardChanges": "Discard", - "saveChanges": "Save changes" + "saveChanges": "Save changes", + "editAsText": "Edit as Text" }, "download": { "downloadFile": "Download File", @@ -75,7 +76,9 @@ "sortByLastModified": "Sort by last modified", "sortByName": "Sort by name", "sortBySize": "Sort by size", - "noPreview": "Preview is not available for this file." + "noPreview": "Preview is not available for this file.", + "csvTooLarge": "CSV file is too large for preview (>5MB). Please download to view.", + "csvLoadFailed": "Failed to load CSV file." }, "help": { "click": "select file or directory", diff --git a/filebrowser/frontend/src/utils/csv.ts b/filebrowser/frontend/src/utils/csv.ts new file mode 100644 index 0000000000..5a55e501e6 --- /dev/null +++ b/filebrowser/frontend/src/utils/csv.ts @@ -0,0 +1,61 @@ +export interface CsvData { + headers: string[]; + rows: string[][]; +} + +/** + * Parse CSV content into headers and rows + * Supports quoted fields and handles commas within quotes + */ +export function parseCSV(content: string): CsvData { + if (!content || content.trim().length === 0) { + return { headers: [], rows: [] }; + } + + const lines = content.split(/\r?\n/); + const result: string[][] = []; + + for (const line of lines) { + if (line.trim().length === 0) continue; + + const row: string[] = []; + let currentField = ""; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + const nextChar = line[i + 1]; + + if (char === '"') { + if (inQuotes && nextChar === '"') { + // Escaped quote + currentField += '"'; + i++; // Skip next quote + } else { + // Toggle quote state + inQuotes = !inQuotes; + } + } else if (char === "," && !inQuotes) { + // Field separator + row.push(currentField); + currentField = ""; + } else { + currentField += char; + } + } + + // Add the last field + row.push(currentField); + result.push(row); + } + + if (result.length === 0) { + return { headers: [], rows: [] }; + } + + // First row is headers + const headers = result[0]; + const rows = result.slice(1); + + return { headers, rows }; +} diff --git a/filebrowser/frontend/src/views/Files.vue b/filebrowser/frontend/src/views/Files.vue index 5001659fa8..8952f2097d 100644 --- a/filebrowser/frontend/src/views/Files.vue +++ b/filebrowser/frontend/src/views/Files.vue @@ -69,6 +69,12 @@ const currentView = computed(() => { if (fileStore.req.isDir) { return FileListing; + } else if (fileStore.req.extension.toLowerCase() === ".csv") { + // CSV files use Preview for table view, unless ?edit=true + if (route.query.edit === "true") { + return Editor; + } + return Preview; } else if ( fileStore.req.type === "text" || fileStore.req.type === "textImmutable" diff --git a/filebrowser/frontend/src/views/files/Preview.vue b/filebrowser/frontend/src/views/files/Preview.vue index 69cbc489c0..0956285693 100644 --- a/filebrowser/frontend/src/views/files/Preview.vue +++ b/filebrowser/frontend/src/views/files/Preview.vue @@ -6,7 +6,7 @@ @mousemove="toggleNavigation" @touchstart="toggleNavigation" > - + {{ name }} + {{ size }}% + (false); const autoPlay = ref(false); const previousRaw = ref(""); const nextRaw = ref(""); +const csvContent = ref(""); +const csvError = ref(""); const player = ref(null); @@ -248,6 +264,8 @@ const authStore = useAuthStore(); const fileStore = useFileStore(); const layoutStore = useLayoutStore(); +const { t } = useI18n(); + const route = useRoute(); const router = useRouter(); @@ -279,6 +297,7 @@ const isPdf = computed(() => fileStore.req?.extension.toLowerCase() == ".pdf"); const isEpub = computed( () => fileStore.req?.extension.toLowerCase() == ".epub" ); +const isCsv = computed(() => fileStore.req?.extension.toLowerCase() == ".csv"); const isResizeEnabled = computed(() => resizePreview); @@ -366,6 +385,18 @@ const updatePreview = async () => { const dirs = route.fullPath.split("/"); name.value = decodeURIComponent(dirs[dirs.length - 1]); + // Load CSV content if it's a CSV file + if (isCsv.value && fileStore.req) { + csvContent.value = ""; + csvError.value = ""; + + if (fileStore.req.size > CSV_MAX_SIZE) { + csvError.value = t("files.csvTooLarge"); + } else { + csvContent.value = fileStore.req.content ?? ""; + } + } + if (!listing.value) { try { const path = url.removeLastDir(route.path); @@ -435,4 +466,8 @@ const close = () => { }; const download = () => window.open(downloadUrl.value); + +const editAsText = () => { + router.push({ path: route.path, query: { edit: "true" } }); +}; diff --git a/mieru/Makefile b/mieru/Makefile index d530c4a312..eebdad1425 100644 --- a/mieru/Makefile +++ b/mieru/Makefile @@ -32,7 +32,7 @@ PROJECT_NAME=$(shell basename "${ROOT}") # - pkg/version/current.go # # Use `tools/bump_version.sh` script to change all those files at one shot. -VERSION="3.24.0" +VERSION="3.24.1" # With .ONESHELL, each recipe is executed in a single shell instance. # This allows `cd` to affect subsequent commands in the same recipe. diff --git a/mieru/build/package/mieru/amd64/debian/DEBIAN/control b/mieru/build/package/mieru/amd64/debian/DEBIAN/control index b56b7e9ee6..d71d88c5e9 100755 --- a/mieru/build/package/mieru/amd64/debian/DEBIAN/control +++ b/mieru/build/package/mieru/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 3.24.0 +Version: 3.24.1 Section: net Priority: optional Architecture: amd64 diff --git a/mieru/build/package/mieru/amd64/rpm/mieru.spec b/mieru/build/package/mieru/amd64/rpm/mieru.spec index 07b49d919a..30a15af685 100644 --- a/mieru/build/package/mieru/amd64/rpm/mieru.spec +++ b/mieru/build/package/mieru/amd64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 3.24.0 +Version: 3.24.1 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/mieru/build/package/mieru/arm64/debian/DEBIAN/control b/mieru/build/package/mieru/arm64/debian/DEBIAN/control index 91159d163a..59164c959b 100755 --- a/mieru/build/package/mieru/arm64/debian/DEBIAN/control +++ b/mieru/build/package/mieru/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mieru -Version: 3.24.0 +Version: 3.24.1 Section: net Priority: optional Architecture: arm64 diff --git a/mieru/build/package/mieru/arm64/rpm/mieru.spec b/mieru/build/package/mieru/arm64/rpm/mieru.spec index 07b49d919a..30a15af685 100644 --- a/mieru/build/package/mieru/arm64/rpm/mieru.spec +++ b/mieru/build/package/mieru/arm64/rpm/mieru.spec @@ -1,5 +1,5 @@ Name: mieru -Version: 3.24.0 +Version: 3.24.1 Release: 1%{?dist} Summary: Mieru proxy client License: GPLv3+ diff --git a/mieru/build/package/mita/amd64/debian/DEBIAN/control b/mieru/build/package/mita/amd64/debian/DEBIAN/control index 4cf5b40d36..08b665bc68 100755 --- a/mieru/build/package/mita/amd64/debian/DEBIAN/control +++ b/mieru/build/package/mita/amd64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 3.24.0 +Version: 3.24.1 Section: net Priority: optional Architecture: amd64 diff --git a/mieru/build/package/mita/amd64/rpm/mita.spec b/mieru/build/package/mita/amd64/rpm/mita.spec index 08a07200ea..d77caa49aa 100644 --- a/mieru/build/package/mita/amd64/rpm/mita.spec +++ b/mieru/build/package/mita/amd64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 3.24.0 +Version: 3.24.1 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/mieru/build/package/mita/arm64/debian/DEBIAN/control b/mieru/build/package/mita/arm64/debian/DEBIAN/control index d10e8d9a0d..2a970af574 100755 --- a/mieru/build/package/mita/arm64/debian/DEBIAN/control +++ b/mieru/build/package/mita/arm64/debian/DEBIAN/control @@ -1,5 +1,5 @@ Package: mita -Version: 3.24.0 +Version: 3.24.1 Section: net Priority: optional Architecture: arm64 diff --git a/mieru/build/package/mita/arm64/rpm/mita.spec b/mieru/build/package/mita/arm64/rpm/mita.spec index e75f8df978..aa35964452 100644 --- a/mieru/build/package/mita/arm64/rpm/mita.spec +++ b/mieru/build/package/mita/arm64/rpm/mita.spec @@ -1,5 +1,5 @@ Name: mita -Version: 3.24.0 +Version: 3.24.1 Release: 1%{?dist} Summary: Mieru proxy server License: GPLv3+ diff --git a/mieru/docs/server-install.md b/mieru/docs/server-install.md index 17bf958ea8..b7cbad8c29 100644 --- a/mieru/docs/server-install.md +++ b/mieru/docs/server-install.md @@ -18,32 +18,32 @@ Or you can manually install and configure proxy server using the steps below. ```sh # Debian / Ubuntu - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita_3.24.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_amd64.deb # Debian / Ubuntu - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita_3.24.0_arm64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita-3.24.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita-3.24.0-1.aarch64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.aarch64.rpm ``` ## Install mita package ```sh # Debian / Ubuntu - X86_64 -sudo dpkg -i mita_3.24.0_amd64.deb +sudo dpkg -i mita_3.24.1_amd64.deb # Debian / Ubuntu - ARM 64 -sudo dpkg -i mita_3.24.0_arm64.deb +sudo dpkg -i mita_3.24.1_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -sudo rpm -Uvh --force mita-3.24.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-3.24.1-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -sudo rpm -Uvh --force mita-3.24.0-1.aarch64.rpm +sudo rpm -Uvh --force mita-3.24.1-1.aarch64.rpm ``` Those instructions can also be used to upgrade the version of mita software package. diff --git a/mieru/docs/server-install.zh_CN.md b/mieru/docs/server-install.zh_CN.md index affc48d240..5ca092fd74 100644 --- a/mieru/docs/server-install.zh_CN.md +++ b/mieru/docs/server-install.zh_CN.md @@ -18,32 +18,32 @@ sudo python3 setup.py --lang=zh ```sh # Debian / Ubuntu - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita_3.24.0_amd64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_amd64.deb # Debian / Ubuntu - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita_3.24.0_arm64.deb +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita_3.24.1_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita-3.24.0-1.x86_64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.0/mita-3.24.0-1.aarch64.rpm +curl -LSO https://github.com/enfein/mieru/releases/download/v3.24.1/mita-3.24.1-1.aarch64.rpm ``` ## 安装 mita 软件包 ```sh # Debian / Ubuntu - X86_64 -sudo dpkg -i mita_3.24.0_amd64.deb +sudo dpkg -i mita_3.24.1_amd64.deb # Debian / Ubuntu - ARM 64 -sudo dpkg -i mita_3.24.0_arm64.deb +sudo dpkg -i mita_3.24.1_arm64.deb # RedHat / CentOS / Rocky Linux - X86_64 -sudo rpm -Uvh --force mita-3.24.0-1.x86_64.rpm +sudo rpm -Uvh --force mita-3.24.1-1.x86_64.rpm # RedHat / CentOS / Rocky Linux - ARM 64 -sudo rpm -Uvh --force mita-3.24.0-1.aarch64.rpm +sudo rpm -Uvh --force mita-3.24.1-1.aarch64.rpm ``` 上述指令也可以用来升级 mita 软件包的版本。 diff --git a/mieru/pkg/appctl/appctlpb/servercfg.pb.go b/mieru/pkg/appctl/appctlpb/servercfg.pb.go index 2577e8351c..792cc4499d 100644 --- a/mieru/pkg/appctl/appctlpb/servercfg.pb.go +++ b/mieru/pkg/appctl/appctlpb/servercfg.pb.go @@ -242,11 +242,6 @@ type ServerAdvancedSettings struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Allow using socks5 to access resources served in localhost. - // This option should be set to false unless required due to testing purpose. - // This field has been deprecated, has no effect, and will be removed. - // Instead, use allowPrivateIP and allowLoopbackIP in User configuration. - AllowLocalDestination *bool `protobuf:"varint,1,opt,name=allowLocalDestination,proto3,oneof" json:"allowLocalDestination,omitempty"` // The interval to log metrics. // Examples: 30s, 5m, 2h. // If empty, the default interval is used. @@ -285,13 +280,6 @@ func (*ServerAdvancedSettings) Descriptor() ([]byte, []int) { return file_appctl_proto_servercfg_proto_rawDescGZIP(), []int{1} } -func (x *ServerAdvancedSettings) GetAllowLocalDestination() bool { - if x != nil && x.AllowLocalDestination != nil { - return *x.AllowLocalDestination - } - return false -} - func (x *ServerAdvancedSettings) GetMetricsLoggingInterval() string { if x != nil && x.MetricsLoggingInterval != nil { return *x.MetricsLoggingInterval @@ -604,19 +592,14 @@ var file_appctl_proto_servercfg_proto_rawDesc = []byte{ 0x69, 0x6e, 0x67, 0x73, 0x42, 0x0f, 0x0a, 0x0d, 0x5f, 0x6c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6d, 0x74, 0x75, 0x42, 0x09, 0x0a, 0x07, 0x5f, 0x65, 0x67, 0x72, 0x65, 0x73, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x64, 0x6e, 0x73, - 0x22, 0xc5, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x76, 0x61, 0x6e, - 0x63, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x61, - 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x15, 0x61, 0x6c, - 0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x3b, 0x0a, 0x16, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x16, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, - 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x88, 0x01, 0x01, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x6f, 0x63, - 0x61, 0x6c, 0x44, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x19, 0x0a, - 0x17, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x22, 0x6d, 0x0a, 0x06, 0x45, 0x67, 0x72, 0x65, + 0x22, 0x76, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x76, 0x61, 0x6e, 0x63, + 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x3b, 0x0a, 0x16, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x16, 0x6d, 0x65, + 0x74, 0x72, 0x69, 0x63, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, + 0x72, 0x76, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x42, 0x19, 0x0a, 0x17, 0x5f, 0x6d, 0x65, 0x74, 0x72, + 0x69, 0x63, 0x73, 0x4c, 0x6f, 0x67, 0x67, 0x69, 0x6e, 0x67, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, + 0x61, 0x6c, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x6d, 0x0a, 0x06, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x33, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6d, 0x69, 0x65, 0x72, 0x75, 0x2e, 0x61, 0x70, 0x70, 0x63, 0x74, 0x6c, 0x2e, 0x45, 0x67, 0x72, 0x65, 0x73, 0x73, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x07, diff --git a/mieru/pkg/appctl/proto/servercfg.proto b/mieru/pkg/appctl/proto/servercfg.proto index 6d709ae720..42c301ed41 100644 --- a/mieru/pkg/appctl/proto/servercfg.proto +++ b/mieru/pkg/appctl/proto/servercfg.proto @@ -47,11 +47,8 @@ message ServerConfig { } message ServerAdvancedSettings { - // Allow using socks5 to access resources served in localhost. - // This option should be set to false unless required due to testing purpose. - // This field has been deprecated, has no effect, and will be removed. - // Instead, use allowPrivateIP and allowLoopbackIP in User configuration. - optional bool allowLocalDestination = 1; + // Was: optional bool allowLocalDestination = 1; + reserved 1; // The interval to log metrics. // Examples: 30s, 5m, 2h. diff --git a/mieru/pkg/protocol/session.go b/mieru/pkg/protocol/session.go index 3052d944bf..5ff602c3ea 100644 --- a/mieru/pkg/protocol/session.go +++ b/mieru/pkg/protocol/session.go @@ -127,14 +127,14 @@ type Session struct { recvQueue *segmentTree // segments waiting to be read by application recvChan chan *segment // channel to receive segments from underlay - nextSend uint32 // next sequence number to send a segment - nextRecv uint32 // next sequence number to receive - lastSend uint32 // last segment sequence number sent - lastRXTime time.Time // last timestamp when a segment is received - lastTXTime time.Time // last timestamp when a segment is sent - nextRetransmissionTime time.Time // time that need to retransmit a segment in sendBuf - ackOnDataRecv atomic.Bool // whether ack should be sent due to receive of new data - unreadBuf []byte // payload removed from the recvQueue that haven't been read by application + nextSend atomic.Uint32 // next sequence number to send a segment + nextRecv atomic.Uint32 // next sequence number to receive + lastSend atomic.Uint32 // last segment sequence number sent + lastRXTime time.Time // last timestamp when a segment is received + lastTXTime time.Time // last timestamp when a segment is sent + nextRetransmissionTime time.Time // time that need to retransmit a segment in sendBuf + ackOnDataRecv atomic.Bool // whether ack should be sent due to receive of new data + unreadBuf []byte // payload removed from the recvQueue that haven't been read by application uploadBytes metrics.Metric // number of bytes from client to server, only used by server downloadBytes metrics.Metric // number of bytes from server to client, only used by server @@ -142,7 +142,7 @@ type Session struct { rttStat *congestion.RTTStats cubicSendAlgorithm *congestion.CubicSendAlgorithm bbrSendAlgorithm *congestion.BBRSender - remoteWindowSize uint16 + remoteWindowSize atomic.Uint32 wg sync.WaitGroup rLock sync.Mutex // serialize application read @@ -164,7 +164,7 @@ func NewSession(id uint32, isClient bool, mtu int, users map[string]*appctlpb.Us rttStat := congestion.NewRTTStats() rttStat.SetMaxAckDelay(periodicOutputInterval) rttStat.SetRTOMultiplier(txTimeoutBackOff) - return &Session{ + s := &Session{ conn: nil, block: atomic.Pointer[cipher.BlockCipher]{}, id: id, @@ -189,8 +189,9 @@ func NewSession(id uint32, isClient bool, mtu int, users map[string]*appctlpb.Us rttStat: rttStat, cubicSendAlgorithm: congestion.NewCubicSendAlgorithm(minWindowSize, maxWindowSize), bbrSendAlgorithm: congestion.NewBBRSender(fmt.Sprintf("%d", id), rttStat), - remoteWindowSize: minWindowSize, } + s.remoteWindowSize.Store(minWindowSize) + return s } func (s *Session) String() string { @@ -313,11 +314,11 @@ func (s *Session) Write(b []byte) (n int, err error) { protocol: uint8(openSessionRequest), }, sessionID: s.id, - seq: s.nextSend, + seq: s.nextSend.Load(), }, transport: s.transportProtocol, } - s.nextSend++ + s.nextSend.Add(1) // Allow open session request to carry payload. if len(b) <= MaxSessionOpenPayload { seg.metadata.(*sessionStruct).payloadLen = uint16(len(b)) @@ -433,7 +434,7 @@ func (s *Session) ToSessionInfo() *appctlpb.SessionInfo { Seconds: s.lastRXTime.Unix(), Nanos: int32(s.lastRXTime.Nanosecond()), } - info.LastRecvSeq = proto.Uint32(uint32(mathext.Max(0, int(s.nextRecv)-1))) + info.LastRecvSeq = proto.Uint32(uint32(mathext.Max(0, int(s.nextRecv.Load())-1))) } else { info.Protocol = proto.String("UNKNOWN") } @@ -441,7 +442,7 @@ func (s *Session) ToSessionInfo() *appctlpb.SessionInfo { Seconds: s.lastTXTime.Unix(), Nanos: int32(s.lastTXTime.Nanosecond()), } - info.LastSendSeq = proto.Uint32(uint32(mathext.Max(0, int(s.nextSend)-1))) + info.LastSendSeq = proto.Uint32(uint32(mathext.Max(0, int(s.nextSend.Load())-1))) } return info } @@ -545,8 +546,8 @@ func (s *Session) writeChunk(b []byte) (n int, err error) { protocol: protocol, }, sessionID: s.id, - seq: s.nextSend, - unAckSeq: s.nextRecv, + seq: s.nextSend.Load(), + unAckSeq: s.nextRecv.Load(), windowSize: uint16(s.receiveWindowSize()), fragment: uint8(i), payloadLen: uint16(partLen), @@ -555,7 +556,7 @@ func (s *Session) writeChunk(b []byte) (n int, err error) { transport: s.transportProtocol, } copy(seg.payload, part) - s.nextSend++ + s.nextSend.Add(1) if !s.sendQueue.Insert(seg) { s.oLock.Unlock() return 0, fmt.Errorf("insert %v to send queue failed", seg) @@ -728,7 +729,7 @@ func (s *Session) runOutputOncePacket() { iter.txTimeout = mathext.Min(s.rttStat.RTO()*time.Duration(math.Pow(txTimeoutBackOff, float64(iter.txCount))), maxBackOffDuration) if isDataAckProtocol(iter.metadata.Protocol()) { das, _ := toDataAckStruct(iter.metadata) - das.unAckSeq = s.nextRecv + das.unAckSeq = s.nextRecv.Load() } if err := s.output(iter, s.RemoteAddr()); err != nil { err = fmt.Errorf("output() failed: %w", err) @@ -787,7 +788,7 @@ func (s *Session) runOutputOncePacket() { seg.txTimeout = mathext.Min(s.rttStat.RTO()*time.Duration(math.Pow(txTimeoutBackOff, float64(seg.txCount))), maxBackOffDuration) if isDataAckProtocol(seg.metadata.Protocol()) { das, _ := toDataAckStruct(seg.metadata) - das.unAckSeq = s.nextRecv + das.unAckSeq = s.nextRecv.Load() } if !s.sendBuf.Insert(seg) { s.oLock.Unlock() @@ -845,8 +846,8 @@ func (s *Session) runOutputOncePacket() { metadata: &dataAckStruct{ baseStruct: baseStruct, sessionID: s.id, - seq: uint32(mathext.Max(0, int(s.nextSend)-1)), - unAckSeq: s.nextRecv, + seq: uint32(mathext.Max(0, int(s.nextSend.Load())-1)), + unAckSeq: s.nextRecv.Load(), windowSize: uint16(s.receiveWindowSize()), }, transport: s.transportProtocol} @@ -975,7 +976,7 @@ func (s *Session) inputData(seg *segment) error { if len(ackedPackets) > 0 { s.bbrSendAlgorithm.OnCongestionEvent(priorInFlight, time.Now(), ackedPackets, nil) } - s.remoteWindowSize = das.windowSize + s.remoteWindowSize.Store(uint32(das.windowSize)) } // Deliver the segment to recvBuf. @@ -998,20 +999,20 @@ func (s *Session) inputData(seg *segment) error { seg3, deleted := s.recvBuf.DeleteMinIf(func(iter *segment) bool { seq, _ := iter.Seq() - return seq <= s.nextRecv + return seq <= s.nextRecv.Load() }) if seg3 == nil || !deleted { break } seq, _ := seg3.Seq() - if seq == s.nextRecv { + if seq == s.nextRecv.Load() { if !s.recvQueue.Insert(seg3) { return fmt.Errorf("inputData() failed: insert %v to receive queue failed", seg3) } - s.nextRecv++ + s.nextRecv.Add(1) das, ok := seg3.metadata.(*dataAckStruct) if ok { - s.remoteWindowSize = das.windowSize + s.remoteWindowSize.Store(uint32(das.windowSize)) } } } @@ -1044,11 +1045,11 @@ func (s *Session) inputData(seg *segment) error { protocol: uint8(openSessionResponse), }, sessionID: s.id, - seq: s.nextSend, + seq: s.nextSend.Load(), }, transport: s.transportProtocol, } - s.nextSend++ + s.nextSend.Add(1) if log.IsLevelEnabled(log.TraceLevel) { log.Tracef("%v writing open session response", s) } @@ -1099,7 +1100,7 @@ func (s *Session) inputAck(seg *segment) error { if len(ackedPackets) > 0 { s.bbrSendAlgorithm.OnCongestionEvent(priorInFlight, time.Now(), ackedPackets, nil) } - s.remoteWindowSize = das.windowSize + s.remoteWindowSize.Store(uint32(das.windowSize)) // Update acknowledge count. s.sendBuf.Ascend(func(iter *segment) bool { @@ -1128,13 +1129,13 @@ func (s *Session) inputClose(seg *segment) error { protocol: uint8(closeSessionResponse), }, sessionID: s.id, - seq: s.nextSend, + seq: s.nextSend.Load(), statusCode: uint8(statusOK), payloadLen: 0, }, transport: s.transportProtocol, } - s.nextSend++ + s.nextSend.Add(1) // The response will not retry if it is not delivered. if err := s.output(seg2, s.RemoteAddr()); err != nil { s.oLock.Unlock() @@ -1177,7 +1178,8 @@ func (s *Session) output(seg *segment, remoteAddr net.Addr) error { default: return fmt.Errorf("unsupported transport protocol %v", s.transportProtocol) } - s.lastSend, _ = seg.Seq() + seq, _ := seg.Seq() + s.lastSend.Store(seq) s.lastTXTime = time.Now() return nil } @@ -1199,7 +1201,7 @@ func (s *Session) closeWithError(err error) error { // Send closeSessionRequest, but don't wait for closeSessionResponse, // because the underlay connection may be already broken. s.oLock.Lock() - closeRequestSeq := s.nextSend + closeRequestSeq := s.nextSend.Load() seg := &segment{ metadata: &sessionStruct{ baseStruct: baseStruct{ @@ -1211,7 +1213,7 @@ func (s *Session) closeWithError(err error) error { }, transport: s.transportProtocol, } - s.nextSend++ + s.nextSend.Add(1) var gracefulCloseSuccess bool if gracefulClose { @@ -1221,7 +1223,7 @@ func (s *Session) closeWithError(err error) error { s.oLock.Unlock() for i := 0; i < 1000; i++ { time.Sleep(time.Millisecond) - if s.lastSend >= closeRequestSeq { + if s.lastSend.Load() >= closeRequestSeq { gracefulCloseSuccess = true break } @@ -1252,7 +1254,7 @@ func (s *Session) closeWithError(err error) error { // sendWindowSize determines how many more packets this session can send. func (s *Session) sendWindowSize() int { - return mathext.Max(0, mathext.Min(int(s.cubicSendAlgorithm.CongestionWindowSize())-s.sendBuf.Len(), int(s.remoteWindowSize))) + return mathext.Max(0, mathext.Min(int(s.cubicSendAlgorithm.CongestionWindowSize())-s.sendBuf.Len(), int(s.remoteWindowSize.Load()))) } // receiveWindowSize determines how many more packets this session can receive. diff --git a/mieru/pkg/protocol/underlay_base.go b/mieru/pkg/protocol/underlay_base.go index af60355b9a..e49dbeb1b5 100644 --- a/mieru/pkg/protocol/underlay_base.go +++ b/mieru/pkg/protocol/underlay_base.go @@ -96,7 +96,6 @@ func (b *baseUnderlay) Close() error { s = nil return true }) - b.sessionMap = sync.Map{} close(b.done) UnderlayCurrEstablished.Add(-1) return nil diff --git a/mieru/pkg/version/current.go b/mieru/pkg/version/current.go index 856497348e..d6f60462c5 100644 --- a/mieru/pkg/version/current.go +++ b/mieru/pkg/version/current.go @@ -16,5 +16,5 @@ package version const ( - AppVersion = "3.24.0" + AppVersion = "3.24.1" ) diff --git a/mihomo/adapter/outbound/sudoku.go b/mihomo/adapter/outbound/sudoku.go index 1db9ad2dc5..9b4a9c9db1 100644 --- a/mihomo/adapter/outbound/sudoku.go +++ b/mihomo/adapter/outbound/sudoku.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/metacubex/mihomo/log" + "github.com/saba-futai/sudoku/apis" "github.com/saba-futai/sudoku/pkg/crypto" "github.com/saba-futai/sudoku/pkg/obfs/httpmask" @@ -38,8 +40,8 @@ type SudokuOption struct { AEADMethod string `proxy:"aead-method,omitempty"` PaddingMin *int `proxy:"padding-min,omitempty"` PaddingMax *int `proxy:"padding-max,omitempty"` - Seed string `proxy:"seed,omitempty"` TableType string `proxy:"table-type,omitempty"` // "prefer_ascii" or "prefer_entropy" + HTTPMask bool `proxy:"http-mask,omitempty"` } // DialContext implements C.ProxyAdapter @@ -120,8 +122,10 @@ func (s *Sudoku) buildConfig(metadata *C.Metadata) (*apis.ProtocolConfig, error) } func (s *Sudoku) streamConn(rawConn net.Conn, cfg *apis.ProtocolConfig) (_ net.Conn, err error) { - if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { - return nil, fmt.Errorf("write http mask failed: %w", err) + if !cfg.DisableHTTPMask { + if err = httpmask.WriteRandomRequestHeader(rawConn, cfg.ServerAddress); err != nil { + return nil, fmt.Errorf("write http mask failed: %w", err) + } } obfsConn := sudoku.NewConn(rawConn, cfg.Table, cfg.PaddingMin, cfg.PaddingMax, false) @@ -163,12 +167,14 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { return nil, fmt.Errorf("table-type must be prefer_ascii or prefer_entropy") } - seed := option.Seed - if seed == "" { - seed = option.Key + seed := option.Key + if recoveredFromKey, err := crypto.RecoverPublicKey(option.Key); err == nil { + seed = crypto.EncodePoint(recoveredFromKey) } + start := time.Now() table := sudoku.NewTable(seed, tableType) + log.Infoln("[Sudoku] Tables initialized (%s) in %v", tableType, time.Since(start)) defaultConf := apis.DefaultConfig() paddingMin := defaultConf.PaddingMin @@ -194,6 +200,7 @@ func NewSudoku(option SudokuOption) (*Sudoku, error) { PaddingMin: paddingMin, PaddingMax: paddingMax, HandshakeTimeoutSeconds: defaultConf.HandshakeTimeoutSeconds, + DisableHTTPMask: !option.HTTPMask, } if option.AEADMethod != "" { baseConf.AEADMethod = option.AEADMethod diff --git a/mihomo/adapter/outboundgroup/parser.go b/mihomo/adapter/outboundgroup/parser.go index 2dcdd628b2..4640685594 100644 --- a/mihomo/adapter/outboundgroup/parser.go +++ b/mihomo/adapter/outboundgroup/parser.go @@ -186,7 +186,7 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide strategy := parseStrategy(config) return NewLoadBalance(groupOption, providers, strategy) case "relay": - group = NewRelay(groupOption, providers) + return nil, fmt.Errorf("%w: The group [%s] with relay type was removed, please using dialer-proxy instead", errType, groupName) default: return nil, fmt.Errorf("%w: %s", errType, groupOption.Type) } diff --git a/mihomo/docs/config.yaml b/mihomo/docs/config.yaml index 0d5fb75e22..bef2c65914 100644 --- a/mihomo/docs/config.yaml +++ b/mihomo/docs/config.yaml @@ -1047,8 +1047,8 @@ proxies: # socks5 aead-method: chacha20-poly1305 # 可选值:chacha20-poly1305、aes-128-gcm、none 我们保证在none的情况下sudoku混淆层仍然确保安全 padding-min: 2 # 最小填充字节数 padding-max: 7 # 最大填充字节数 - seed: "" # 如果使用sudoku生成的ED25519密钥对,请填写密钥对中的公钥(如果你有安全焦虑,填入私钥也可以,只是私钥长度比较长不好看而已),否则填入和服务端相同的uuid table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 + http-mask: true # 是否启用http掩码 # anytls - name: anytls @@ -1587,7 +1587,7 @@ listeners: aead-method: chacha20-poly1305 # 支持chacha20-poly1305或者aes-128-gcm以及none,sudoku的混淆层可以确保none情况下数据安全 padding-min: 1 # 填充最小长度 padding-max: 15 # 填充最大长度,均不建议过大 - seed: "" # 如果你不使用ED25519密钥对,就请填入客户端的key,否则仍然是公钥 + seed: "" # 如果你不使用ED25519密钥对,就请填入uuid,否则仍然是公钥 table-type: prefer_ascii # 可选值:prefer_ascii、prefer_entropy 前者全ascii映射,后者保证熵值(汉明1)低于3 handshake-timeout: 5 # optional diff --git a/mihomo/go.mod b/mihomo/go.mod index d7e40c7eae..aaf8890ccf 100644 --- a/mihomo/go.mod +++ b/mihomo/go.mod @@ -6,7 +6,7 @@ require ( github.com/bahlo/generic-list-go v0.2.0 github.com/coreos/go-iptables v0.8.0 github.com/dlclark/regexp2 v1.11.5 - github.com/enfein/mieru/v3 v3.24.0 + github.com/enfein/mieru/v3 v3.24.1 github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/render v1.0.3 github.com/gobwas/ws v1.4.0 @@ -43,7 +43,7 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/openacid/low v0.1.21 github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20 - github.com/saba-futai/sudoku v0.0.1-e + github.com/saba-futai/sudoku v0.0.1-g github.com/sagernet/cors v1.2.1 github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/samber/lo v1.52.0 diff --git a/mihomo/go.sum b/mihomo/go.sum index 15ba6b90eb..1186bda8f2 100644 --- a/mihomo/go.sum +++ b/mihomo/go.sum @@ -25,8 +25,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/enfein/mieru/v3 v3.24.0 h1:UpS6fTj242wAz2Xa/ieavMN8owcWdPzLFB11UqYs5GY= -github.com/enfein/mieru/v3 v3.24.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= +github.com/enfein/mieru/v3 v3.24.1 h1:iX9py3+GExxJxLaxjHAEmQmoE1r0y2hDIsliija+jTI= +github.com/enfein/mieru/v3 v3.24.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo= github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I= github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g= @@ -171,8 +171,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/saba-futai/sudoku v0.0.1-e h1:PetJcOdoybBWGT1k65puNv+kt6Cmger6i/TSfuu6CdM= -github.com/saba-futai/sudoku v0.0.1-e/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= +github.com/saba-futai/sudoku v0.0.1-g h1:4q6OuAA6COaRW+CgoQtdim5AUPzzm0uOkvbYpJnOaBE= +github.com/saba-futai/sudoku v0.0.1-g/go.mod h1:2ZRzRwz93cS2K/o2yOG4CPJEltcvk5y6vbvUmjftGU0= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= diff --git a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/server/log.htm b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/server/log.htm index 024ee335c1..f337408802 100644 --- a/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/server/log.htm +++ b/openwrt-passwall/luci-app-passwall/luasrc/view/passwall/server/log.htm @@ -3,13 +3,19 @@ local api = require "luci.passwall.api" -%>