initial
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
/.idea
|
||||||
@@ -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/` 即可。
|
||||||
@@ -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."
|
||||||
@@ -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.'
|
||||||
Executable
+80
@@ -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."
|
||||||
@@ -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.'
|
||||||
Executable
+33
@@ -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)"
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/sing-box
|
||||||
|
/core
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
REPO_URL=git@git.inker.bot:arknights/sing-box.git
|
||||||
|
UPSTREAM_BRANCH=dev-next
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
.idea
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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:
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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=
|
||||||
@@ -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)
|
||||||
@@ -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"`
|
||||||
@@ -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"`
|
||||||
|
+}
|
||||||
@@ -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,
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
|
+}
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user