From 648aa9336029fb9876b992fd4aee9631ac292a66 Mon Sep 17 00:00:00 2001 From: InkerBot Date: Thu, 5 Mar 2026 13:33:05 +0800 Subject: [PATCH] initial --- .gitignore | 1 + README.md | 41 +++ bin/apply | 68 +++++ bin/apply.ps1 | 74 +++++ bin/generate | 80 +++++ bin/generate.ps1 | 82 +++++ bin/update | 33 ++ bin/update.ps1 | 39 +++ works/.gitignore | 2 + works/config | 2 + works/ignore | 1 + works/patch/cmd/sing-box/cmd_run.go.patch | 47 +++ works/patch/constant/proxy.go.patch | 21 ++ works/patch/go.mod.patch | 52 ++++ works/patch/go.sum.patch | 158 ++++++++++ works/patch/include/registry.go.patch | 26 ++ works/patch/option/group.go.patch | 10 + works/patch/option/mysql.go.patch | 26 ++ works/patch/protocol/group/urltest.go.patch | 80 +++++ works/patch/protocol/mysql/inbound.go.patch | 258 ++++++++++++++++ works/patch/protocol/mysql/outbound.go.patch | 299 +++++++++++++++++++ works/patch/route/route.go.patch | 27 ++ 22 files changed, 1427 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bin/apply create mode 100644 bin/apply.ps1 create mode 100755 bin/generate create mode 100644 bin/generate.ps1 create mode 100755 bin/update create mode 100644 bin/update.ps1 create mode 100644 works/.gitignore create mode 100644 works/config create mode 100644 works/ignore create mode 100644 works/patch/cmd/sing-box/cmd_run.go.patch create mode 100644 works/patch/constant/proxy.go.patch create mode 100644 works/patch/go.mod.patch create mode 100644 works/patch/go.sum.patch create mode 100644 works/patch/include/registry.go.patch create mode 100644 works/patch/option/group.go.patch create mode 100644 works/patch/option/mysql.go.patch create mode 100644 works/patch/protocol/group/urltest.go.patch create mode 100644 works/patch/protocol/mysql/inbound.go.patch create mode 100644 works/patch/protocol/mysql/outbound.go.patch create mode 100644 works/patch/route/route.go.patch diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a09c56d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..b52e904 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +# O.M.V. 帝江号 + +## 开发规范 + +**补丁规范** + +所有的更改必须被 `// OMV start` 和 `// OMV end` 包裹(必须单独起一行),如果单行更改则只需要在行末添加 `// OMV` 即可,新建文件的只需要在文件开头添加 `// OMV` 即可。 + +除非非常简单的更改,否则务必说明更改的内容 `// OMV start: register sql protocol`。 + +所有补丁必须遵循最小化修改的原则,在完成需求的情况下,务必保证对项目的修改最小,改动的数量也需要最小。 + +## 开发指南 + +**初始化** + +```sh +bin/update +``` + +克隆或拉取最新的上游 sing-box 到 `works/sing-box/`。 + +**生成工作目录** + +```sh +bin/apply +``` + +将上游代码同步到 `works/core/`,并应用 `works/patch/` 中的所有补丁。 + +**修改代码** + +直接编辑 `works/core/` 中的文件。 + +**保存修改为补丁** + +```sh +bin/generate +``` + +对比 `works/core/` 与 `works/sing-box/`,将差异写入 `works/patch/`(按原目录结构,每个文件对应一个 `.patch`)。之后提交 `works/patch/` 即可。 diff --git a/bin/apply b/bin/apply new file mode 100755 index 0000000..c0e74e9 --- /dev/null +++ b/bin/apply @@ -0,0 +1,68 @@ +#!/bin/sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +SING_BOX="$ROOT/works/sing-box" +PATCH_DIR="$ROOT/works/patch" +OUTPUT="$ROOT/works/core" + +if [ ! -d "$SING_BOX/.git" ]; then + echo "Error: works/sing-box not found. Run update first." >&2 + exit 1 +fi + +echo "Initializing works/core/..." +rm -rf "$OUTPUT" +mkdir -p "$OUTPUT" +git init "$OUTPUT" + +echo "Syncing sing-box files to works/core/..." +rsync -a --exclude='.git/' "$SING_BOX/" "$OUTPUT/" +VERSION="$(git -C "$SING_BOX" describe --tags --always 2>/dev/null || git -C "$SING_BOX" rev-parse --short HEAD)" +git -C "$OUTPUT" add -A +git -C "$OUTPUT" commit -m "upstream: $VERSION" + +echo "Applying patches..." +applied=0 +failed=0 +failed_list="" + +if [ -d "$PATCH_DIR" ]; then + patch_list="$(mktemp)" + trap 'rm -f "$patch_list"' EXIT + find "$PATCH_DIR" -name "*.patch" | sort > "$patch_list" + + while IFS= read -r patch_file; do + rel="${patch_file#$PATCH_DIR/}" + rel="${rel%.patch}" + dst="$OUTPUT/$rel" + + mkdir -p "$(dirname "$dst")" + if patch --merge --no-backup-if-mismatch --force "$dst" < "$patch_file"; then + applied=$((applied + 1)) + else + echo " FAILED: $rel" >&2 + failed=$((failed + 1)) + failed_list="${failed_list} ${rel} +" + fi + done < "$patch_list" +fi + +git -C "$OUTPUT" add -A +git -C "$OUTPUT" commit -m "patches applied" --allow-empty + +total=$((applied + failed)) +echo "" +echo "Result: $applied/$total patches applied." +if [ "$failed" -gt 0 ]; then + echo "" + echo "Failed patches (merge conflicts written to files):" + printf "%s" "$failed_list" + echo "" + echo "Resolve conflicts in works/core/, then run 'bin/generate' to update patches." + exit 1 +fi + +echo "Done." diff --git a/bin/apply.ps1 b/bin/apply.ps1 new file mode 100644 index 0000000..238ffbd --- /dev/null +++ b/bin/apply.ps1 @@ -0,0 +1,74 @@ +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$Root = Split-Path -Parent $ScriptDir +$SingBox = Join-Path $Root 'works/sing-box' +$PatchDir = Join-Path $Root 'works/patch' +$Output = Join-Path $Root 'works/core' + +if (-not (Test-Path (Join-Path $SingBox '.git'))) { + Write-Error 'works/sing-box not found. Run update first.' + exit 1 +} + +Write-Host 'Initializing works/core/...' +if (Test-Path $Output) { Remove-Item -Recurse -Force $Output } +New-Item -ItemType Directory -Path $Output -Force | Out-Null +git init $Output + +Write-Host 'Syncing sing-box files to works/core/...' +Get-ChildItem -Path $SingBox -Recurse -File | Where-Object { + $_.FullName -notmatch [regex]::Escape((Join-Path $SingBox '.git')) +} | ForEach-Object { + $rel = $_.FullName.Substring($SingBox.Length + 1) + $dst = Join-Path $Output $rel + $dstDir = Split-Path -Parent $dst + if (-not (Test-Path $dstDir)) { New-Item -ItemType Directory -Path $dstDir -Force | Out-Null } + Copy-Item -Path $_.FullName -Destination $dst -Force +} +$version = git -C $SingBox describe --tags --always 2>$null +if (-not $version) { $version = git -C $SingBox rev-parse --short HEAD } +git -C $Output add -A +git -C $Output commit -m "upstream: $version" + +Write-Host 'Applying patches...' +$applied = 0 +$failed = 0 +$failedList = @() + +if (Test-Path $PatchDir) { + Get-ChildItem -Path $PatchDir -Recurse -Filter '*.patch' | Sort-Object FullName | ForEach-Object { + $rel = $_.FullName.Substring($PatchDir.Length + 1) -replace '\.patch$', '' + $dst = Join-Path $Output $rel + $dstDir = Split-Path -Parent $dst + if (-not (Test-Path $dstDir)) { New-Item -ItemType Directory -Path $dstDir -Force | Out-Null } + + & patch --merge --no-backup-if-mismatch --force -i $_.FullName $dst 2>&1 + if ($LASTEXITCODE -eq 0) { + $applied++ + } else { + Write-Host " FAILED: $rel" -ForegroundColor Red + $failed++ + $failedList += $rel + } + } +} + +git -C $Output add -A +git -C $Output commit -m 'patches applied' --allow-empty + +$total = $applied + $failed +Write-Host "" +Write-Host "Result: $applied/$total patches applied." +if ($failed -gt 0) { + Write-Host "" + Write-Host "Failed patches (merge conflicts written to files):" -ForegroundColor Red + foreach ($f in $failedList) { + Write-Host " $f" -ForegroundColor Red + } + Write-Host "" + Write-Host "Resolve conflicts in works/core/, then run 'bin/generate' to update patches." + exit 1 +} + +Write-Host 'Done.' diff --git a/bin/generate b/bin/generate new file mode 100755 index 0000000..ff2cdad --- /dev/null +++ b/bin/generate @@ -0,0 +1,80 @@ +#!/bin/sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +SING_BOX="$ROOT/works/sing-box" +PATCH_DIR="$ROOT/works/patch" +CORE="$ROOT/works/core" + +if [ ! -d "$SING_BOX/.git" ]; then + echo "Error: works/sing-box not found. Run update first." >&2 + exit 1 +fi + +if [ ! -d "$CORE" ]; then + echo "Error: works/core not found. Run generate first." >&2 + exit 1 +fi + +IGNORE_FILE="$ROOT/works/ignore" + +is_ignored() { + [ -f "$IGNORE_FILE" ] || return 1 + _rel="$1" + _name="${_rel##*/}" + while IFS= read -r _pattern || [ -n "$_pattern" ]; do + case "$_pattern" in + ''|\#*) continue ;; + esac + case "$_pattern" in + */*) + case "$_rel" in $_pattern) return 0 ;; esac + ;; + *) + case "$_name" in $_pattern) return 0 ;; esac + ;; + esac + done < "$IGNORE_FILE" + return 1 +} + +echo "Clearing existing patches..." +rm -rf "$PATCH_DIR" +mkdir -p "$PATCH_DIR" + +echo "Generating patches for modified/new files..." +find "$CORE" -not -path "$CORE/.git/*" -type f | sort | while read -r dst_file; do + rel="${dst_file#$CORE/}" + if is_ignored "$rel"; then continue; fi + src_file="$SING_BOX/$rel" + patch_file="$PATCH_DIR/$rel.patch" + + if [ -f "$src_file" ]; then + if ! diff -q "$src_file" "$dst_file" > /dev/null 2>&1; then + mkdir -p "$(dirname "$patch_file")" + diff -u "$src_file" "$dst_file" > "$patch_file" || true + echo " Modified: $rel" + fi + else + mkdir -p "$(dirname "$patch_file")" + diff -u /dev/null "$dst_file" > "$patch_file" || true + echo " New: $rel" + fi +done + +echo "Generating patches for deleted files..." +find "$SING_BOX" -not -path "$SING_BOX/.git/*" -type f | sort | while read -r src_file; do + rel="${src_file#$SING_BOX/}" + if is_ignored "$rel"; then continue; fi + dst_file="$CORE/$rel" + patch_file="$PATCH_DIR/$rel.patch" + + if [ ! -f "$dst_file" ]; then + mkdir -p "$(dirname "$patch_file")" + diff -u "$src_file" /dev/null > "$patch_file" || true + echo " Deleted: $rel" + fi +done + +echo "Done." diff --git a/bin/generate.ps1 b/bin/generate.ps1 new file mode 100644 index 0000000..bec7664 --- /dev/null +++ b/bin/generate.ps1 @@ -0,0 +1,82 @@ +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$Root = Split-Path -Parent $ScriptDir +$SingBox = Join-Path $Root 'works/sing-box' +$PatchDir = Join-Path $Root 'works/patch' +$Core = Join-Path $Root 'works/core' + +if (-not (Test-Path (Join-Path $SingBox '.git'))) { + Write-Error 'works/sing-box not found. Run update first.' + exit 1 +} + +if (-not (Test-Path $Core)) { + Write-Error 'works/core not found. Run generate first.' + exit 1 +} + +$IgnoreFile = Join-Path $Root 'works/ignore' +$IgnorePatterns = @() +if (Test-Path $IgnoreFile) { + $IgnorePatterns = Get-Content $IgnoreFile | Where-Object { $_ -ne '' -and $_ -notmatch '^\s*#' } +} + +function Test-Ignored { + param([string]$RelPath) + foreach ($pattern in $IgnorePatterns) { + if ($pattern -match '/') { + if ($RelPath -like $pattern) { return $true } + } else { + if ((Split-Path -Leaf $RelPath) -like $pattern) { return $true } + } + } + return $false +} + +Write-Host 'Clearing existing patches...' +if (Test-Path $PatchDir) { Remove-Item -Recurse -Force $PatchDir } +New-Item -ItemType Directory -Path $PatchDir -Force | Out-Null + +Write-Host 'Generating patches for modified/new files...' +Get-ChildItem -Path $Core -Recurse -File | Where-Object { + $_.FullName -notmatch [regex]::Escape((Join-Path $Core '.git')) +} | Sort-Object FullName | ForEach-Object { + $rel = $_.FullName.Substring($Core.Length + 1) -replace '\\', '/' + if (Test-Ignored $rel) { return } + $srcFile = Join-Path $SingBox $rel + $patchFile = Join-Path $PatchDir "$rel.patch" + $patchFileDir = Split-Path -Parent $patchFile + if (-not (Test-Path $patchFileDir)) { New-Item -ItemType Directory -Path $patchFileDir -Force | Out-Null } + + if (Test-Path $srcFile) { + $srcHash = (Get-FileHash $srcFile -Algorithm MD5).Hash + $dstHash = (Get-FileHash $_.FullName -Algorithm MD5).Hash + if ($srcHash -ne $dstHash) { + diff -u $srcFile $_.FullName | Out-File -FilePath $patchFile -Encoding utf8 + Write-Host " Modified: $rel" + } + } else { + diff -u /dev/null $_.FullName | Out-File -FilePath $patchFile -Encoding utf8 + Write-Host " New: $rel" + } +} + +Write-Host 'Generating patches for deleted files...' +Get-ChildItem -Path $SingBox -Recurse -File | Where-Object { + $_.FullName -notmatch [regex]::Escape((Join-Path $SingBox '.git')) +} | Sort-Object FullName | ForEach-Object { + $rel = $_.FullName.Substring($SingBox.Length + 1) -replace '\\', '/' + if (Test-Ignored $rel) { return } + $dstFile = Join-Path $Core $rel + $patchFile = Join-Path $PatchDir "$rel.patch" + + if (-not (Test-Path $dstFile)) { + $patchFileDir = Split-Path -Parent $patchFile + if (-not (Test-Path $patchFileDir)) { New-Item -ItemType Directory -Path $patchFileDir -Force | Out-Null } + diff -u $_.FullName /dev/null | Out-File -FilePath $patchFile -Encoding utf8 + Write-Host " Deleted: $rel" + } +} + +Write-Host 'Done.' diff --git a/bin/update b/bin/update new file mode 100755 index 0000000..40292d0 --- /dev/null +++ b/bin/update @@ -0,0 +1,33 @@ +#!/bin/sh +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +SING_BOX="$ROOT/works/sing-box" + +# defaults +REPO_URL="git@git.inker.bot:arknights/sing-box.git" +UPSTREAM_BRANCH="main" + +CONFIG="$ROOT/works/config" +if [ -f "$CONFIG" ]; then + . "$CONFIG" +fi + +if [ -d "$SING_BOX/.git" ]; then + echo "Updating sing-box..." + git -C "$SING_BOX" fetch --all + if [ -n "$UPSTREAM_BRANCH" ]; then + git -C "$SING_BOX" checkout "$UPSTREAM_BRANCH" + fi + git -C "$SING_BOX" pull +else + echo "Cloning sing-box..." + if [ -n "$UPSTREAM_BRANCH" ]; then + git clone -b "$UPSTREAM_BRANCH" "$REPO_URL" "$SING_BOX" + else + git clone "$REPO_URL" "$SING_BOX" + fi +fi + +echo "Done. Version: $(git -C "$SING_BOX" describe --tags --always 2>/dev/null || git -C "$SING_BOX" rev-parse --short HEAD)" diff --git a/bin/update.ps1 b/bin/update.ps1 new file mode 100644 index 0000000..d06b218 --- /dev/null +++ b/bin/update.ps1 @@ -0,0 +1,39 @@ +$ErrorActionPreference = 'Stop' + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$Root = Split-Path -Parent $ScriptDir +$SingBox = Join-Path $Root 'works/sing-box' + +# defaults +$RepoUrl = 'git@git.inker.bot:arknights/sing-box.git' +$UpstreamBranch = 'main' + +$ConfigFile = Join-Path $Root 'works/config' +if (Test-Path $ConfigFile) { + Get-Content $ConfigFile | ForEach-Object { + if ($_ -match '^([^#=]+)=(.*)$') { + $key = $Matches[1].Trim() + $val = $Matches[2].Trim() + if ($key -eq 'REPO_URL') { $RepoUrl = $val } + elseif ($key -eq 'UPSTREAM_BRANCH') { $UpstreamBranch = $val } + } + } +} + +if (Test-Path (Join-Path $SingBox '.git')) { + Write-Host 'Updating sing-box...' + git -C $SingBox fetch --all + if ($UpstreamBranch) { git -C $SingBox checkout $UpstreamBranch } + git -C $SingBox pull +} else { + Write-Host 'Cloning sing-box...' + if ($UpstreamBranch) { + git clone -b $UpstreamBranch $RepoUrl $SingBox + } else { + git clone $RepoUrl $SingBox + } +} + +$version = git -C $SingBox describe --tags --always 2>$null +if (-not $version) { $version = git -C $SingBox rev-parse --short HEAD } +Write-Host "Done. Version: $version" diff --git a/works/.gitignore b/works/.gitignore new file mode 100644 index 0000000..f58a781 --- /dev/null +++ b/works/.gitignore @@ -0,0 +1,2 @@ +/sing-box +/core diff --git a/works/config b/works/config new file mode 100644 index 0000000..0d17862 --- /dev/null +++ b/works/config @@ -0,0 +1,2 @@ +REPO_URL=git@git.inker.bot:arknights/sing-box.git +UPSTREAM_BRANCH=dev-next diff --git a/works/ignore b/works/ignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/works/ignore @@ -0,0 +1 @@ +.idea diff --git a/works/patch/cmd/sing-box/cmd_run.go.patch b/works/patch/cmd/sing-box/cmd_run.go.patch new file mode 100644 index 0000000..79c3492 --- /dev/null +++ b/works/patch/cmd/sing-box/cmd_run.go.patch @@ -0,0 +1,47 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/cmd/sing-box/cmd_run.go 2026-03-05 11:39:22.614241536 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/cmd/sing-box/cmd_run.go 2026-03-05 13:09:50.466040286 +0800 +@@ -3,6 +3,7 @@ + import ( + "context" + "io" ++ "net/http" // OMV + "os" + "os/signal" + "path/filepath" +@@ -49,8 +50,36 @@ + configContent []byte + err error + ) ++ // OMV start: fetch subscription from env ++ subscriptionLink := os.Getenv("SING_SUBSCRIPTION_LINK") ++ subscriptionCache := os.Getenv("SING_SUBSCRIPTION_CACHE") ++ // OMV end + if path == "stdin" { + configContent, err = io.ReadAll(os.Stdin) ++ // OMV start: fetch subscription from env ++ } else if path == "env" && subscriptionLink != "" { ++ configContentResponse, err := http.Get(subscriptionLink) ++ if err != nil { ++ log.Warn("Cannot fetch subscription: ", err) ++ } ++ if err != nil || configContentResponse.StatusCode != 200 { ++ if subscriptionCache != "" { ++ log.Info("using previous subscription cache") ++ configContent, err = os.ReadFile(subscriptionCache) ++ } else { ++ return nil, E.Cause(err, "failed to GET subscription link") ++ } ++ } else { ++ defer configContentResponse.Body.Close() ++ configContent, err = io.ReadAll(configContentResponse.Body) ++ if err == nil && subscriptionCache != "" { ++ _err := os.WriteFile(subscriptionCache, configContent, 0600) ++ if _err != nil { ++ log.Error(E.Cause(_err, "failed to cache subscription file")) ++ } ++ } ++ } ++ // OMV end + } else { + configContent, err = os.ReadFile(path) + } diff --git a/works/patch/constant/proxy.go.patch b/works/patch/constant/proxy.go.patch new file mode 100644 index 0000000..54d6b96 --- /dev/null +++ b/works/patch/constant/proxy.go.patch @@ -0,0 +1,21 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/constant/proxy.go 2026-03-05 11:39:22.619163709 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/constant/proxy.go 2026-03-05 13:11:43.857690519 +0800 +@@ -31,6 +31,7 @@ + TypeCCM = "ccm" + TypeOCM = "ocm" + TypeOOMKiller = "oom-killer" ++ TypeMySQL = "mysql" // OMV + ) + + const ( +@@ -88,6 +89,10 @@ + return "AnyTLS" + case TypeTailscale: + return "Tailscale" ++ // OMV start: register mysql type name ++ case TypeMySQL: ++ return "MySQL" ++ // OMV end + case TypeSelector: + return "Selector" + case TypeURLTest: diff --git a/works/patch/go.mod.patch b/works/patch/go.mod.patch new file mode 100644 index 0000000..4572871 --- /dev/null +++ b/works/patch/go.mod.patch @@ -0,0 +1,52 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/go.mod 2026-03-05 12:48:33.529768921 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/go.mod 2026-03-05 13:02:19.454323175 +0800 +@@ -11,6 +11,7 @@ + github.com/database64128/tfo-go/v2 v2.3.2 + github.com/go-chi/chi/v5 v5.2.5 + github.com/go-chi/render v1.0.3 ++ github.com/go-mysql-org/go-mysql v1.13.0 + github.com/godbus/dbus/v5 v5.2.2 + github.com/gofrs/uuid/v5 v5.4.0 + github.com/insomniacslk/dhcp v0.0.0-20260220084031-5adc3eb26f91 +@@ -85,6 +86,7 @@ + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect ++ github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect +@@ -100,6 +102,9 @@ + github.com/mdlayher/socket v0.5.1 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/pierrec/lz4/v4 v4.1.21 // indirect ++ github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec // indirect ++ github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a // indirect ++ github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d // indirect + github.com/pires/go-proxyproto v0.8.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus-community/pro-bing v0.4.0 // indirect +@@ -136,6 +141,7 @@ + github.com/sagernet/cronet-go/lib/windows_arm64 v0.0.0-20260303100323-125d0d93b3e6 // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect ++ github.com/shopspring/decimal v1.2.0 // indirect + github.com/spf13/pflag v1.0.9 // indirect + github.com/tailscale/certstore v0.1.1-0.20231202035212-d3fa0460f47e // indirect + github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 // indirect +@@ -151,6 +157,7 @@ + github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/zeebo/blake3 v0.2.4 // indirect ++ go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap/exp v0.3.0 // indirect + go4.org/mem v0.0.0-20240501181205-ae6ca9944745 // indirect +@@ -163,6 +170,7 @@ + golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 // indirect + golang.zx2c4.com/wireguard/windows v0.5.3 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect ++ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/blake3 v1.3.0 // indirect + ) diff --git a/works/patch/go.sum.patch b/works/patch/go.sum.patch new file mode 100644 index 0000000..860deae --- /dev/null +++ b/works/patch/go.sum.patch @@ -0,0 +1,158 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/go.sum 2026-03-05 12:48:33.529768921 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/go.sum 2026-03-05 13:02:19.455279219 +0800 +@@ -14,6 +14,7 @@ + github.com/anthropics/anthropic-sdk-go v1.26.0/go.mod h1:qUKmaW+uuPB64iy1l+4kOSvaLqPXnHTTBKH6RVZ7q5Q= + github.com/anytls/sing-anytls v0.0.11 h1:w8e9Uj1oP3m4zxkyZDewPk0EcQbvVxb7Nn+rapEx4fc= + github.com/anytls/sing-anytls v0.0.11/go.mod h1:7rjN6IukwysmdusYsrV51Fgu1uW6vsrdd6ctjnEAln8= ++github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= + github.com/caddyserver/certmagic v0.25.2 h1:D7xcS7ggX/WEY54x0czj7ioTkmDWKIgxtIi2OcQclUc= + github.com/caddyserver/certmagic v0.25.2/go.mod h1:llW/CvsNmza8S6hmsuggsZeiX+uS27dkqY27wDIuBWg= + github.com/caddyserver/zerossl v0.1.5 h1:dkvOjBAEEtY6LIGAHei7sw2UgqSD6TrWweXpV7lvEvE= +@@ -36,6 +37,7 @@ + github.com/database64128/tfo-go/v2 v2.3.2 h1:UhZMKiMq3swZGUiETkLBDzQnZBPSAeBMClpJGlnJ5Fw= + github.com/database64128/tfo-go/v2 v2.3.2/go.mod h1:GC3uB5oa4beGpCUbRb2ZOWP73bJJFmMyAVgQSO7r724= + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= ++github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= + github.com/dblohm7/wingoes v0.0.0-20240119213807-a09d6be7affa h1:h8TfIT1xc8FWbwwpmHn1J5i43Y0uZP97GqasGCzSRJk= +@@ -68,12 +70,14 @@ + github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= ++github.com/go-mysql-org/go-mysql v1.13.0/go.mod h1:FQxw17uRbFvMZFK+dPtIPufbU46nBdrGaxOw0ac9MFs= + github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= + github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= + github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= + github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= + github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= + github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= ++github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= + github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= + github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= + github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0= +@@ -110,6 +114,9 @@ + github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= + github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= + github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= ++github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= ++github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= ++github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= + github.com/letsencrypt/challtestsrv v1.4.2 h1:0ON3ldMhZyWlfVNYYpFuWRTmZNnyfiL9Hh5YzC3JVwU= + github.com/letsencrypt/challtestsrv v1.4.2/go.mod h1:GhqMqcSoeGpYd5zX5TgwA6er/1MbWzx/o7yuuVya+Wk= + github.com/letsencrypt/pebble/v2 v2.10.0 h1:Wq6gYXlsY6ubqI3hhxsTzdyotvfdjFBxuwYqCLCnj/U= +@@ -144,8 +151,13 @@ + github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= + github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= + github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= ++github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= ++github.com/pingcap/errors v0.11.5-0.20250318082626-8f80e5cb09ec/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= ++github.com/pingcap/log v1.1.1-0.20241212030209-7e3ff8601a2a/go.mod h1:ORfBOFp1eteu2odzsyaxI+b8TzJwgjwyQcGhI+9SfEA= ++github.com/pingcap/tidb/pkg/parser v0.0.0-20250421232622-526b2c79173d/go.mod h1:+8feuexTKcXHZF/dkDfvCwEyBAmgb4paFc3/WeYV2eE= + github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= + github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= ++github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +@@ -260,11 +272,14 @@ + github.com/sagernet/wireguard-go v0.0.2-beta.1.0.20260224074747-506b7631853c/go.mod h1:WUxgxUDZoCF2sxVmW+STSxatP02Qn3FcafTiI2BLtE0= + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= ++github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= + github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= + github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= + github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= + github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= ++github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= ++github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= + github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= + github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= + github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +@@ -319,10 +334,18 @@ + go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= + go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= + go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= ++go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= ++go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= ++go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= ++go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= ++go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= + go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= ++go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= ++go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= + go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= + go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= ++go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= + go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= + go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= + go.uber.org/zap/exp v0.3.0 h1:6JYzdifzYkGmTdRR59oYH+Ng7k49H9qVpWwNSsGJj3U= +@@ -332,6 +355,7 @@ + go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= ++golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= + golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= + golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= + golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +@@ -339,17 +363,22 @@ + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= + golang.org/x/image v0.27.0 h1:C8gA4oWU/tKkdCfYT6T2u4faJu3MeNS5O8UPWlPF61w= + golang.org/x/image v0.27.0/go.mod h1:xbdrClrAUway1MUTEZDq9mz/UpRwYAkFFNUslZtcB+g= ++golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= + golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= + golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= ++golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= ++golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= + golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= + golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= + golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= + golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= + golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= + golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= ++golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= + golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= + golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= ++golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= + golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= + golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= + golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +@@ -361,6 +390,7 @@ + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= + golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= + golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= ++golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= + golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= + golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +@@ -368,8 +398,12 @@ + golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= + golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= ++golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= ++golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= ++golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= + golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= + golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= ++golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +@@ -389,10 +423,14 @@ + google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= ++gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= ++gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= + gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= ++gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= + gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= + gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ++gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= + howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= diff --git a/works/patch/include/registry.go.patch b/works/patch/include/registry.go.patch new file mode 100644 index 0000000..265d42c --- /dev/null +++ b/works/patch/include/registry.go.patch @@ -0,0 +1,26 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/include/registry.go 2026-03-05 12:48:33.529768921 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/include/registry.go 2026-03-05 13:14:06.319924033 +0800 +@@ -23,6 +23,7 @@ + "github.com/sagernet/sing-box/protocol/group" + "github.com/sagernet/sing-box/protocol/http" + "github.com/sagernet/sing-box/protocol/mixed" ++ "github.com/sagernet/sing-box/protocol/mysql" // OMV + "github.com/sagernet/sing-box/protocol/naive" + "github.com/sagernet/sing-box/protocol/redirect" + "github.com/sagernet/sing-box/protocol/shadowsocks" +@@ -62,6 +63,7 @@ + shadowtls.RegisterInbound(registry) + vless.RegisterInbound(registry) + anytls.RegisterInbound(registry) ++ mysql.RegisterInbound(registry) // OMV + + registerQUICInbounds(registry) + registerStubForRemovedInbounds(registry) +@@ -90,6 +92,7 @@ + shadowtls.RegisterOutbound(registry) + vless.RegisterOutbound(registry) + anytls.RegisterOutbound(registry) ++ mysql.RegisterOutbound(registry) // OMV + + registerQUICOutbounds(registry) + registerStubForRemovedOutbounds(registry) diff --git a/works/patch/option/group.go.patch b/works/patch/option/group.go.patch new file mode 100644 index 0000000..96b9ec3 --- /dev/null +++ b/works/patch/option/group.go.patch @@ -0,0 +1,10 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/option/group.go 2026-03-05 11:39:22.629561767 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/option/group.go 2026-03-05 13:14:27.190665236 +0800 +@@ -10,6 +10,7 @@ + + type URLTestOutboundOptions struct { + Outbounds []string `json:"outbounds"` ++ Costs map[string]uint16 `json:"costs,omitempty"` // OMV + URL string `json:"url,omitempty"` + Interval badoption.Duration `json:"interval,omitempty"` + Tolerance uint16 `json:"tolerance,omitempty"` diff --git a/works/patch/option/mysql.go.patch b/works/patch/option/mysql.go.patch new file mode 100644 index 0000000..64ffc1e --- /dev/null +++ b/works/patch/option/mysql.go.patch @@ -0,0 +1,26 @@ +--- /dev/null 2026-03-05 11:06:53.990718185 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/option/mysql.go 2026-03-05 13:14:38.655523071 +0800 +@@ -0,0 +1,23 @@ ++// OMV ++package option ++ ++type MySQLInboundOptions struct { ++ ListenOptions ++ InboundTLSOptionsContainer ++ Users []MySQLUser `json:"users,omitempty"` ++ Multiplex *InboundMultiplexOptions `json:"multiplex,omitempty"` ++} ++ ++type MySQLUser struct { ++ User string `json:"user,omitempty"` ++ Password string `json:"password,omitempty"` ++} ++ ++type MySQLOutboundOptions struct { ++ DialerOptions ++ ServerOptions ++ OutboundTLSOptionsContainer ++ Username string `json:"username,omitempty"` ++ Password string `json:"password,omitempty"` ++ Multiplex *OutboundMultiplexOptions `json:"multiplex,omitempty"` ++} diff --git a/works/patch/protocol/group/urltest.go.patch b/works/patch/protocol/group/urltest.go.patch new file mode 100644 index 0000000..492d55a --- /dev/null +++ b/works/patch/protocol/group/urltest.go.patch @@ -0,0 +1,80 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/protocol/group/urltest.go 2026-03-05 11:39:22.630699577 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/protocol/group/urltest.go 2026-03-05 13:17:32.300369913 +0800 +@@ -39,6 +39,7 @@ + connection adapter.ConnectionManager + logger log.ContextLogger + tags []string ++ costs map[string]uint16 // OMV + link string + interval time.Duration + tolerance uint16 +@@ -61,6 +62,7 @@ + tolerance: options.Tolerance, + idleTimeout: time.Duration(options.IdleTimeout), + interruptExternalConnections: options.InterruptExistConnections, ++ costs: options.Costs, // OMV + } + if len(outbound.tags) == 0 { + return nil, E.New("missing tags") +@@ -77,7 +79,7 @@ + } + outbounds = append(outbounds, detour) + } +- group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) ++ group, err := NewURLTestGroup(s.ctx, s.outbound, s.logger, outbounds, s.costs, s.link, s.interval, s.tolerance, s.idleTimeout, s.interruptExternalConnections) // OMV + if err != nil { + return err + } +@@ -194,6 +196,7 @@ + pauseCallback *list.Element[pause.Callback] + logger log.Logger + outbounds []adapter.Outbound ++ costs map[string]uint16 // OMV + link string + interval time.Duration + tolerance uint16 +@@ -211,7 +214,7 @@ + lastActive common.TypedValue[time.Time] + } + +-func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { ++func NewURLTestGroup(ctx context.Context, outboundManager adapter.OutboundManager, logger log.Logger, outbounds []adapter.Outbound, costs map[string]uint16, link string, interval time.Duration, tolerance uint16, idleTimeout time.Duration, interruptExternalConnections bool) (*URLTestGroup, error) { // OMV + if interval == 0 { + interval = C.DefaultURLTestInterval + } +@@ -221,6 +224,11 @@ + if idleTimeout == 0 { + idleTimeout = C.DefaultURLTestIdleTimeout + } ++ // OMV start: initialize costs map ++ if costs == nil { ++ costs = map[string]uint16{} ++ } ++ // OMV end + if interval > idleTimeout { + return nil, E.New("interval must be less or equal than idle_timeout") + } +@@ -237,6 +245,7 @@ + outbound: outboundManager, + logger: logger, + outbounds: outbounds, ++ costs: costs, // OMV + link: link, + interval: interval, + tolerance: tolerance, +@@ -392,7 +401,14 @@ + g.logger.Debug("outbound ", tag, " unavailable: ", err) + g.history.DeleteURLTestHistory(realTag) + } else { +- g.logger.Debug("outbound ", tag, " available: ", t, "ms") ++ // OMV start: apply cost penalty to url test latency ++ add_latency, exist := g.costs[tag] ++ if !exist { ++ add_latency = 0 ++ } ++ t = t + add_latency ++ g.logger.Debug("outbound ", tag, " available: ", t, "ms (add latency: ", add_latency, "ms)") ++ // OMV end + g.history.StoreURLTestHistory(realTag, &adapter.URLTestHistory{ + Time: time.Now(), + Delay: t, diff --git a/works/patch/protocol/mysql/inbound.go.patch b/works/patch/protocol/mysql/inbound.go.patch new file mode 100644 index 0000000..078ef92 --- /dev/null +++ b/works/patch/protocol/mysql/inbound.go.patch @@ -0,0 +1,258 @@ +--- /dev/null 2026-03-05 11:06:53.990718185 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/protocol/mysql/inbound.go 2026-03-05 13:14:59.956258942 +0800 +@@ -0,0 +1,255 @@ ++// OMV ++package mysql ++ ++import ( ++ "context" ++ "net" ++ "os" ++ ++ "github.com/sagernet/sing-box/adapter" ++ "github.com/sagernet/sing-box/adapter/inbound" ++ "github.com/sagernet/sing-box/common/listener" ++ "github.com/sagernet/sing-box/common/mux" ++ boxTLS "github.com/sagernet/sing-box/common/tls" ++ "github.com/sagernet/sing-box/common/uot" ++ C "github.com/sagernet/sing-box/constant" ++ "github.com/sagernet/sing-box/log" ++ "github.com/sagernet/sing-box/option" ++ "github.com/sagernet/sing/common" ++ E "github.com/sagernet/sing/common/exceptions" ++ "github.com/sagernet/sing/common/logger" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/task" ++ "github.com/sagernet/smux" ++ ++ gmysql "github.com/go-mysql-org/go-mysql/mysql" ++ "github.com/go-mysql-org/go-mysql/server" ++) ++ ++func RegisterInbound(registry *inbound.Registry) { ++ inbound.Register[option.MySQLInboundOptions](registry, C.TypeMySQL, NewInbound) ++} ++ ++var _ adapter.TCPInjectableInbound = (*Inbound)(nil) ++ ++type Inbound struct { ++ inbound.Adapter ++ router adapter.ConnectionRouterEx ++ logger logger.ContextLogger ++ listener *listener.Listener ++ tlsConfig boxTLS.ServerConfig ++ identityProvider *server.InMemoryProvider ++ mysqlServer *server.Server ++} ++ ++func NewInbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MySQLInboundOptions) (adapter.Inbound, error) { ++ inbound := &Inbound{ ++ Adapter: inbound.NewAdapter(C.TypeMySQL, tag), ++ router: uot.NewRouter(router, logger), ++ logger: logger, ++ identityProvider: server.NewInMemoryProvider(), ++ } ++ for _, user := range options.Users { ++ inbound.identityProvider.AddUser(user.User, user.Password) ++ } ++ ++ if options.TLS == nil || !options.TLS.Enabled { ++ return nil, C.ErrTLSRequired ++ } ++ ++ tlsConfig, err := boxTLS.NewServer(ctx, logger, common.PtrValueOrDefault(options.TLS)) ++ if err != nil { ++ return nil, err ++ } ++ inbound.tlsConfig = tlsConfig ++ ++ // Get the standard *tls.Config from our TLS config for go-mysql server ++ stdTLSConfig, err := tlsConfig.STDConfig() ++ if err != nil { ++ return nil, E.Cause(err, "get std tls config") ++ } ++ ++ // Create a go-mysql server with our TLS config ++ inbound.mysqlServer = server.NewServer( ++ "8.0.12", ++ gmysql.DEFAULT_COLLATION_ID, ++ gmysql.AUTH_NATIVE_PASSWORD, ++ nil, ++ stdTLSConfig, ++ ) ++ ++ inbound.router, err = mux.NewRouterWithOptions(inbound.router, logger, common.PtrValueOrDefault(options.Multiplex)) ++ if err != nil { ++ return nil, err ++ } ++ ++ inbound.listener = listener.New(listener.Options{ ++ Context: ctx, ++ Logger: logger, ++ Network: []string{N.NetworkTCP}, ++ Listen: options.ListenOptions, ++ ConnectionHandler: inbound, ++ }) ++ return inbound, nil ++} ++ ++func (h *Inbound) Start(stage adapter.StartStage) error { ++ if stage != adapter.StartStateStart { ++ return nil ++ } ++ if h.tlsConfig != nil { ++ err := h.tlsConfig.Start() ++ if err != nil { ++ return E.Cause(err, "create TLS config") ++ } ++ } ++ return h.listener.Start() ++} ++ ++func (h *Inbound) Close() error { ++ return common.Close( ++ h.listener, ++ h.tlsConfig, ++ ) ++} ++ ++func (h *Inbound) NewConnectionEx(ctx context.Context, conn net.Conn, metadata adapter.InboundContext, onClose N.CloseHandlerFunc) { ++ // Use go-mysql server to perform the MySQL handshake (which negotiates TLS) ++ mysqlConn, err := h.mysqlServer.NewCustomizedConn(conn, h.identityProvider, &emptyHandler{}) ++ if err != nil { ++ N.CloseOnHandshakeFailure(conn, onClose, err) ++ h.logger.ErrorContext(ctx, E.Cause(err, "process connection from ", metadata.Source, ": MySQL handshake")) ++ return ++ } ++ ++ // After MySQL handshake, the underlying connection is TLS-encrypted. ++ // Now get the underlying net.Conn (which is a *tls.Conn) and use smux on top of it. ++ tlsConn := mysqlConn.Conn.Conn ++ ++ h.logger.InfoContext(ctx, "MySQL handshake completed from ", metadata.Source) ++ ++ // Handle smux session over the TLS-encrypted connection ++ err = h.handleMuxSession(ctx, tlsConn, metadata.Source, onClose, mysqlConn.GetUser()) ++ if err != nil && !E.IsClosed(err) { ++ h.logger.ErrorContext(ctx, E.Cause(err, "process mux session from ", metadata.Source)) ++ } ++} ++ ++func (h *Inbound) handleMuxSession(ctx context.Context, conn net.Conn, source M.Socksaddr, onClose N.CloseHandlerFunc, user string) error { ++ session, err := smux.Server(conn, smuxConfig()) ++ if err != nil { ++ if onClose != nil { ++ onClose(err) ++ } ++ return err ++ } ++ var group task.Group ++ group.Append0(func(_ context.Context) error { ++ for { ++ stream, sErr := session.AcceptStream() ++ if sErr != nil { ++ return sErr ++ } ++ go h.handleMuxStream(ctx, stream, source, user) ++ } ++ }) ++ group.Cleanup(func() { ++ session.Close() ++ if onClose != nil { ++ onClose(os.ErrClosed) ++ } ++ }) ++ return group.Run(ctx) ++} ++ ++func (h *Inbound) handleMuxStream(ctx context.Context, conn net.Conn, source M.Socksaddr, user string) { ++ err := h.handleMuxStream0(ctx, conn, source, user) ++ if err != nil { ++ h.logger.ErrorContext(ctx, E.Cause(err, "process mux stream")) ++ } ++} ++ ++func (h *Inbound) handleMuxStream0(ctx context.Context, conn net.Conn, source M.Socksaddr, user string) error { ++ // Read destination from the stream header: ++ // 1 byte command (0x01=TCP, 0x03=UDP) ++ // then socks address (using SocksaddrSerializer) ++ var cmdBuf [1]byte ++ _, err := conn.Read(cmdBuf[:]) ++ if err != nil { ++ return E.Cause(err, "read command") ++ } ++ command := cmdBuf[0] ++ ++ destination, err := M.SocksaddrSerializer.ReadAddrPort(conn) ++ if err != nil { ++ return E.Cause(err, "read destination") ++ } ++ ++ var metadata adapter.InboundContext ++ metadata.Inbound = h.Tag() ++ metadata.InboundType = h.Type() ++ metadata.Source = source ++ metadata.User = user ++ ++ switch command { ++ case commandTCP: ++ metadata.Destination = destination ++ h.logger.InfoContext(ctx, "inbound connection to ", metadata.Destination) ++ h.router.RouteConnectionEx(ctx, conn, metadata, nil) ++ case commandUDP: ++ metadata.Destination = destination ++ h.logger.InfoContext(ctx, "inbound UoT packet connection to ", metadata.Destination) ++ h.router.RouteConnectionEx(ctx, conn, metadata, nil) ++ default: ++ return E.New("unknown command ", command) ++ } ++ return nil ++} ++ ++func smuxConfig() *smux.Config { ++ config := smux.DefaultConfig() ++ config.KeepAliveDisabled = true ++ return config ++} ++ ++const ( ++ commandTCP byte = 0x01 ++ commandUDP byte = 0x03 ++) ++ ++// emptyHandler implements go-mysql server.Handler with no-op operations. ++// It is used because we only need the MySQL handshake for TLS negotiation, ++// not actual MySQL query handling. ++type emptyHandler struct{} ++ ++func (h *emptyHandler) UseDB(dbName string) error { ++ return nil ++} ++ ++func (h *emptyHandler) HandleQuery(query string) (*gmysql.Result, error) { ++ return nil, gmysql.NewError(gmysql.ER_UNKNOWN_ERROR, "not supported") ++} ++ ++func (h *emptyHandler) HandleFieldList(table string, fieldWildcard string) ([]*gmysql.Field, error) { ++ return nil, gmysql.NewError(gmysql.ER_UNKNOWN_ERROR, "not supported") ++} ++ ++func (h *emptyHandler) HandleStmtPrepare(query string) (int, int, interface{}, error) { ++ return 0, 0, nil, gmysql.NewError(gmysql.ER_UNKNOWN_ERROR, "not supported") ++} ++ ++func (h *emptyHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*gmysql.Result, error) { ++ return nil, gmysql.NewError(gmysql.ER_UNKNOWN_ERROR, "not supported") ++} ++ ++func (h *emptyHandler) HandleStmtClose(context interface{}) error { ++ return nil ++} ++ ++func (h *emptyHandler) HandleOtherCommand(cmd byte, data []byte) error { ++ return gmysql.NewError(gmysql.ER_UNKNOWN_ERROR, "not supported") ++} ++ ++// compile-time check ++var _ server.Handler = (*emptyHandler)(nil) diff --git a/works/patch/protocol/mysql/outbound.go.patch b/works/patch/protocol/mysql/outbound.go.patch new file mode 100644 index 0000000..6438d86 --- /dev/null +++ b/works/patch/protocol/mysql/outbound.go.patch @@ -0,0 +1,299 @@ +--- /dev/null 2026-03-05 11:06:53.990718185 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/protocol/mysql/outbound.go 2026-03-05 13:15:28.015911006 +0800 +@@ -0,0 +1,296 @@ ++// OMV ++package mysql ++ ++import ( ++ "context" ++ "crypto/tls" ++ "net" ++ "sync" ++ "sync/atomic" ++ ++ "github.com/sagernet/sing-box/adapter" ++ "github.com/sagernet/sing-box/adapter/outbound" ++ "github.com/sagernet/sing-box/common/dialer" ++ C "github.com/sagernet/sing-box/constant" ++ "github.com/sagernet/sing-box/log" ++ "github.com/sagernet/sing-box/option" ++ "github.com/sagernet/sing/common" ++ E "github.com/sagernet/sing/common/exceptions" ++ "github.com/sagernet/sing/common/logger" ++ M "github.com/sagernet/sing/common/metadata" ++ N "github.com/sagernet/sing/common/network" ++ "github.com/sagernet/sing/common/uot" ++ "github.com/sagernet/smux" ++ ++ "github.com/go-mysql-org/go-mysql/client" ++) ++ ++func RegisterOutbound(registry *outbound.Registry) { ++ outbound.Register[option.MySQLOutboundOptions](registry, C.TypeMySQL, NewOutbound) ++} ++ ++var _ adapter.InterfaceUpdateListener = (*Outbound)(nil) ++ ++type Outbound struct { ++ outbound.Adapter ++ ctx context.Context ++ logger logger.ContextLogger ++ dialer N.Dialer ++ serverAddr M.Socksaddr ++ username string ++ password string ++ tlsConfig *tls.Config ++ maxConnections int ++ nextSession uint32 ++ ++ sessionAccess sync.Mutex ++ sessions []*muxSession ++} ++ ++type muxSession struct { ++ session *smux.Session ++ conn net.Conn ++} ++ ++func closeMuxSession(entry *muxSession) { ++ if entry == nil { ++ return ++ } ++ _ = common.Close(entry.session, entry.conn) ++} ++ ++func NewOutbound(ctx context.Context, router adapter.Router, logger log.ContextLogger, tag string, options option.MySQLOutboundOptions) (adapter.Outbound, error) { ++ outboundDialer, err := dialer.New(ctx, options.DialerOptions, options.ServerIsDomain()) ++ if err != nil { ++ return nil, err ++ } ++ ++ outbound := &Outbound{ ++ Adapter: outbound.NewAdapterWithDialerOptions(C.TypeMySQL, tag, []string{N.NetworkTCP, N.NetworkUDP}, options.DialerOptions), ++ ctx: ctx, ++ logger: logger, ++ dialer: outboundDialer, ++ serverAddr: options.ServerOptions.Build(), ++ username: options.Username, ++ password: options.Password, ++ maxConnections: 1, ++ } ++ ++ if options.Multiplex != nil && options.Multiplex.Enabled && options.Multiplex.MaxConnections > 1 { ++ outbound.maxConnections = options.Multiplex.MaxConnections ++ } ++ outbound.sessions = make([]*muxSession, outbound.maxConnections) ++ ++ if outbound.serverAddr.Port == 0 { ++ outbound.serverAddr.Port = 3306 ++ } ++ ++ if outbound.username == "" { ++ outbound.username = "root" ++ } ++ ++ // Build TLS config for MySQL client handshake ++ if options.TLS != nil && options.TLS.Enabled { ++ outbound.tlsConfig = &tls.Config{ ++ InsecureSkipVerify: options.TLS.Insecure, ++ ServerName: options.TLS.ServerName, ++ } ++ if outbound.tlsConfig.ServerName == "" { ++ outbound.tlsConfig.ServerName = options.Server ++ } ++ } else { ++ // Default: use insecure TLS (since this is for tunneling, not real MySQL) ++ outbound.tlsConfig = &tls.Config{ ++ InsecureSkipVerify: true, ++ } ++ } ++ ++ return outbound, nil ++} ++ ++func (h *Outbound) createSession() (*muxSession, error) { ++ h.logger.InfoContext(h.ctx, "creating smux session") ++ // Dial TCP connection to server ++ conn, err := h.dialer.DialContext(h.ctx, N.NetworkTCP, h.serverAddr) ++ if err != nil { ++ return nil, E.Cause(err, "dial server") ++ } ++ ++ // Perform MySQL handshake with TLS ++ mysqlConn, err := client.ConnectWithDialer( ++ h.ctx, ++ "tcp", ++ h.serverAddr.String(), ++ h.username, ++ h.password, ++ "", ++ func(ctx context.Context, network, address string) (net.Conn, error) { ++ // Return the already-established connection ++ return conn, nil ++ }, ++ func(c *client.Conn) error { ++ c.SetTLSConfig(h.tlsConfig) ++ return nil ++ }, ++ ) ++ if err != nil { ++ conn.Close() ++ return nil, E.Cause(err, "MySQL handshake") ++ } ++ ++ // After MySQL handshake, the underlying connection is TLS-encrypted. ++ // Get the underlying net.Conn. ++ tlsConn := mysqlConn.Conn.Conn ++ ++ // Create smux session over the TLS connection ++ session, err := smux.Client(tlsConn, smuxConfig()) ++ if err != nil { ++ tlsConn.Close() ++ return nil, E.Cause(err, "create mux session") ++ } ++ ++ return &muxSession{session: session, conn: tlsConn}, nil ++} ++ ++func (h *Outbound) getSession(index int) (*smux.Session, error) { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ ++ entry := h.sessions[index] ++ if entry != nil && !entry.session.IsClosed() { ++ return entry.session, nil ++ } ++ if entry != nil { ++ closeMuxSession(entry) ++ h.sessions[index] = nil ++ } ++ ++ entry, err := h.createSession() ++ if err != nil { ++ return nil, err ++ } ++ h.sessions[index] = entry ++ ++ go func(index int, session *smux.Session, conn net.Conn) { ++ // When session is closed, clean up ++ <-session.CloseChan() ++ h.sessionAccess.Lock() ++ if current := h.sessions[index]; current != nil && current.session == session { ++ h.sessions[index] = nil ++ } ++ h.sessionAccess.Unlock() ++ _ = common.Close(session, conn) ++ }(index, entry.session, entry.conn) ++ ++ return entry.session, nil ++} ++ ++func (h *Outbound) invalidateSession(index int, session *smux.Session) { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ ++ if current := h.sessions[index]; current != nil && current.session == session { ++ h.sessions[index] = nil ++ closeMuxSession(current) ++ } ++} ++ ++func (h *Outbound) openStream(ctx context.Context, command byte, destination M.Socksaddr) (net.Conn, error) { ++ _ = ctx ++ start := int(atomic.AddUint32(&h.nextSession, 1)-1) % h.maxConnections ++ var lastErr error ++ for i := 0; i < h.maxConnections; i++ { ++ index := (start + i) % h.maxConnections ++ session, err := h.getSession(index) ++ if err != nil { ++ lastErr = err ++ continue ++ } ++ ++ stream, err := session.OpenStream() ++ if err != nil { ++ h.invalidateSession(index, session) ++ lastErr = err ++ continue ++ } ++ ++ // Write stream header: command + destination ++ _, err = stream.Write([]byte{command}) ++ if err != nil { ++ stream.Close() ++ lastErr = E.Cause(err, "write stream header command") ++ continue ++ } ++ err = M.SocksaddrSerializer.WriteAddrPort(stream, destination) ++ if err != nil { ++ stream.Close() ++ lastErr = E.Cause(err, "write stream header destination") ++ continue ++ } ++ ++ return stream, nil ++ } ++ if lastErr == nil { ++ lastErr = E.New("open mux stream") ++ } ++ return nil, E.Cause(lastErr, "open mux stream") ++} ++ ++func (h *Outbound) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { ++ switch N.NetworkName(network) { ++ case N.NetworkTCP: ++ h.logger.InfoContext(ctx, "outbound connection to ", destination) ++ return h.openStream(ctx, commandTCP, destination) ++ case N.NetworkUDP: ++ h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) ++ conn, err := h.openStream(ctx, commandUDP, uot.RequestDestination(uot.Version)) ++ if err != nil { ++ return nil, err ++ } ++ return uot.NewLazyConn(conn, uot.Request{ ++ IsConnect: true, ++ Destination: destination, ++ }), nil ++ default: ++ return nil, E.New("unsupported network: ", network) ++ } ++} ++ ++func (h *Outbound) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { ++ h.logger.InfoContext(ctx, "outbound UoT packet connection to ", destination) ++ conn, err := h.openStream(ctx, commandUDP, uot.RequestDestination(uot.Version)) ++ if err != nil { ++ return nil, err ++ } ++ return uot.NewLazyConn(conn, uot.Request{ ++ IsConnect: false, ++ Destination: destination, ++ }), nil ++} ++ ++func (h *Outbound) InterfaceUpdated() { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ for i, session := range h.sessions { ++ if session == nil { ++ continue ++ } ++ session.session.Close() ++ session.conn.Close() ++ h.sessions[i] = nil ++ } ++} ++ ++func (h *Outbound) Close() error { ++ h.sessionAccess.Lock() ++ defer h.sessionAccess.Unlock() ++ var err error ++ for i, session := range h.sessions { ++ if session == nil { ++ continue ++ } ++ err = common.Close(session.session, session.conn) ++ h.sessions[i] = nil ++ } ++ return err ++} diff --git a/works/patch/route/route.go.patch b/works/patch/route/route.go.patch new file mode 100644 index 0000000..3599fee --- /dev/null +++ b/works/patch/route/route.go.patch @@ -0,0 +1,27 @@ +--- /data/projects/arknights/omv-dijiang/works/sing-box/route/route.go 2026-03-05 12:48:33.530768910 +0800 ++++ /data/projects/arknights/omv-dijiang/works/core/route/route.go 2026-03-05 13:21:58.162073283 +0800 +@@ -421,19 +421,19 @@ + } else { + if processInfo.ProcessPath != "" { + if processInfo.UserName != "" { +- r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) ++ r.logger.DebugContext(ctx, "found process path: ", processInfo.ProcessPath, ", user: ", processInfo.UserName) // OMV: info -> debug + } else if processInfo.UserId != -1 { +- r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) ++ r.logger.DebugContext(ctx, "found process path: ", processInfo.ProcessPath, ", user id: ", processInfo.UserId) // OMV: info -> debug + } else { +- r.logger.InfoContext(ctx, "found process path: ", processInfo.ProcessPath) ++ r.logger.DebugContext(ctx, "found process path: ", processInfo.ProcessPath) // OMV: info -> debug + } + } else if processInfo.AndroidPackageName != "" { + r.logger.InfoContext(ctx, "found package name: ", processInfo.AndroidPackageName) + } else if processInfo.UserId != -1 { + if processInfo.UserName != "" { +- r.logger.InfoContext(ctx, "found user: ", processInfo.UserName) ++ r.logger.DebugContext(ctx, "found user: ", processInfo.UserName) // OMV: info -> debug + } else { +- r.logger.InfoContext(ctx, "found user id: ", processInfo.UserId) ++ r.logger.DebugContext(ctx, "found user id: ", processInfo.UserId) // OMV: info -> debug + } + } + metadata.ProcessInfo = processInfo