mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-28 17:49:55 +00:00
Compare commits
143 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84086a6e6c | ||
|
|
443200a51e | ||
|
|
aca0d97beb | ||
|
|
2605bf78f9 | ||
|
|
d2395fb43a | ||
|
|
e3d9a8e2fd | ||
|
|
1ae050ca3b | ||
|
|
7f38763e22 | ||
|
|
2a8831b0d0 | ||
|
|
cdf5e0c73e | ||
|
|
48f3ea8bc9 | ||
|
|
375e160368 | ||
|
|
b31664beeb | ||
|
|
7960bcae15 | ||
|
|
664ddb8d55 | ||
|
|
e4dfe09744 | ||
|
|
b56068ee1c | ||
|
|
99e888c829 | ||
|
|
873d0deeaa | ||
|
|
7e0a77c99c | ||
|
|
5f09db2655 | ||
|
|
10174d281c | ||
|
|
12c30acdda | ||
|
|
2790481709 | ||
|
|
182f60d424 | ||
|
|
930c70f065 | ||
|
|
fc61715e4e | ||
|
|
438be2d379 | ||
|
|
4e20ed65f2 | ||
|
|
ce760fcf19 | ||
|
|
0f76fdf4c5 | ||
|
|
03f4513f61 | ||
|
|
26f603057f | ||
|
|
b481eca4a4 | ||
|
|
eb028b65fc | ||
|
|
48c1b1cdb2 | ||
|
|
76e40baebc | ||
|
|
946b4025df | ||
|
|
089766b285 | ||
|
|
b643388539 | ||
|
|
0836ec6ee3 | ||
|
|
eeb2ad8dae | ||
|
|
71290b057f | ||
|
|
41b321dfe1 | ||
|
|
a18e99f966 | ||
|
|
f90d0b954c | ||
|
|
0408da2aee | ||
|
|
335d54e488 | ||
|
|
d11f9c895c | ||
|
|
e54ca7ceca | ||
|
|
ce82d49c25 | ||
|
|
8e6be1992b | ||
|
|
0e9102daae | ||
|
|
46dccf26d1 | ||
|
|
854c6a13c3 | ||
|
|
b4c3bbf660 | ||
|
|
6c726d6436 | ||
|
|
a0bdb861a9 | ||
|
|
eca5a27774 | ||
|
|
16d95df100 | ||
|
|
9b90719ddd | ||
|
|
7392529677 | ||
|
|
dc52c38179 | ||
|
|
d7999a32d3 | ||
|
|
b41ea05481 | ||
|
|
e6fe895190 | ||
|
|
adf553a958 | ||
|
|
2a915a5c94 | ||
|
|
1b0c72bfab | ||
|
|
e89af723cd | ||
|
|
e8fddd85af | ||
|
|
f04af734e3 | ||
|
|
00035302a1 | ||
|
|
578e659bb9 | ||
|
|
0f1baeb935 | ||
|
|
16ff9e815b | ||
|
|
5f1f296213 | ||
|
|
66fd5c9f0c | ||
|
|
c3a3009a8c | ||
|
|
01cd7e2c0e | ||
|
|
deec7aafe5 | ||
|
|
a9b7e705f0 | ||
|
|
fb043df1b6 | ||
|
|
748b5df902 | ||
|
|
8cbae59d55 | ||
|
|
a37440c81b | ||
|
|
dbb002a5ba | ||
|
|
1a84153213 | ||
|
|
dfe6e0509b | ||
|
|
b6dde7ded7 | ||
|
|
9f1da11792 | ||
|
|
63ad95e10f | ||
|
|
b06ec5bef8 | ||
|
|
d4fbffd8e8 | ||
|
|
305020175d | ||
|
|
79decdc253 | ||
|
|
407c13b8a4 | ||
|
|
d84b182be3 | ||
|
|
8f18d3f6db | ||
|
|
b9260e06b8 | ||
|
|
6337151207 | ||
|
|
aa555ced5f | ||
|
|
349b773b40 | ||
|
|
300eb8b12a | ||
|
|
2b84dd3618 | ||
|
|
6a620ba287 | ||
|
|
56c3462b76 | ||
|
|
6f4fe71e41 | ||
|
|
ba3e7187a6 | ||
|
|
0d92b6724b | ||
|
|
241ae92bce | ||
|
|
91985c1ef8 | ||
|
|
6a9d428991 | ||
|
|
765cbbcc01 | ||
|
|
5b975275f5 | ||
|
|
166392fe17 | ||
|
|
5c6aa433ca | ||
|
|
2c55dc2557 | ||
|
|
56c0b088e8 | ||
|
|
5344e869a8 | ||
|
|
6cfaf15cbf | ||
|
|
31f0060b30 | ||
|
|
c60750d549 | ||
|
|
ebf5918e94 | ||
|
|
93ca18517c | ||
|
|
32d447ce99 | ||
|
|
617fef84ae | ||
|
|
d19199322d | ||
|
|
87795e3a07 | ||
|
|
85bb40aaf8 | ||
|
|
082bcec281 | ||
|
|
9283cb0f5f | ||
|
|
ae7967f662 | ||
|
|
01f8f2db2f | ||
|
|
255ff5e977 | ||
|
|
939e4109d7 | ||
|
|
40587b62b8 | ||
|
|
85e6d25de5 | ||
|
|
29a37f4f4b | ||
|
|
2f9a3b3469 | ||
|
|
40ea0ba098 | ||
|
|
8d7f947a80 | ||
|
|
71a8705636 |
105
.github/workflows/build.yml
vendored
105
.github/workflows/build.yml
vendored
@@ -29,14 +29,20 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
jobs:
|
||||
- { goos: darwin, goarch: arm64, output: arm64 }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1 }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2 }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3 }
|
||||
- { goos: darwin, goarch: arm64, output: arm64 }
|
||||
|
||||
- { goos: linux, goarch: '386', go386: sse2, output: '386', debian: i386, rpm: i386}
|
||||
- { goos: linux, goarch: '386', go386: softfloat, output: '386-softfloat' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible} # old style file name will be removed in next released
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1, debian: amd64, rpm: x86_64, pacman: x86_64, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3, debian: amd64, rpm: x86_64, pacman: x86_64}
|
||||
- { goos: linux, goarch: arm64, output: arm64, debian: arm64, rpm: aarch64, pacman: aarch64}
|
||||
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
|
||||
- { goos: linux, goarch: arm, goarm: '6', output: armv6, debian: armel, rpm: armv6hl}
|
||||
@@ -54,13 +60,19 @@ jobs:
|
||||
- { goos: linux, goarch: ppc64le, output: ppc64le, debian: ppc64el, rpm: ppc64le }
|
||||
|
||||
- { goos: windows, goarch: '386', output: '386' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1 }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2 }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3 }
|
||||
- { goos: windows, goarch: arm64, output: arm64 }
|
||||
|
||||
- { goos: freebsd, goarch: '386', output: '386' }
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-compatible } # old style file name will be removed in next released
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v1, output: amd64-v1 }
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v2, output: amd64-v2 }
|
||||
- { goos: freebsd, goarch: amd64, goamd64: v3, output: amd64-v3 }
|
||||
- { goos: freebsd, goarch: arm64, output: arm64 }
|
||||
|
||||
- { goos: android, goarch: '386', ndk: i686-linux-android34, output: '386' }
|
||||
@@ -68,49 +80,70 @@ jobs:
|
||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
||||
|
||||
# Go 1.24 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
|
||||
- { goos: windows, goarch: '386', output: '386-go124', goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
|
||||
|
||||
# Go 1.23 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||
- { goos: windows, goarch: '386', output: '386-go123', goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go123, goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go123, goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go123, goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go123, goversion: '1.23' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go123, goversion: '1.23' }
|
||||
|
||||
# Go 1.22 with special patch can work on Windows 7
|
||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go122, goversion: '1.22' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go122, goversion: '1.22' }
|
||||
|
||||
# Go 1.21 can revert commit `9e4385` to work on Windows 7
|
||||
# https://github.com/golang/go/issues/64622#issuecomment-1847475161
|
||||
# (OR we can just use golang1.21.4 which unneeded any patch)
|
||||
- { goos: windows, goarch: '386', output: '386-go121', goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go121, goversion: '1.21' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go121, goversion: '1.21' }
|
||||
|
||||
# Go 1.20 is the last release that will run on any release of Windows 7, 8, Server 2008 and Server 2012. Go 1.21 will require at least Windows 10 or Server 2016.
|
||||
- { goos: windows, goarch: '386', output: '386-go120', goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
|
||||
|
||||
# Go 1.24 is the last release that will run on macOS 11 Big Sur. Go 1.25 will require macOS 12 Monterey or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go124, goversion: '1.24' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go124, goversion: '1.24' }
|
||||
|
||||
# Go 1.22 is the last release that will run on macOS 10.15 Catalina. Go 1.23 will require macOS 11 Big Sur or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go122, goversion: '1.22' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go122, goversion: '1.22' }
|
||||
|
||||
# Go 1.20 is the last release that will run on macOS 10.13 High Sierra or 10.14 Mojave. Go 1.21 will require macOS 10.15 Catalina or later.
|
||||
- { goos: darwin, goarch: arm64, output: arm64-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
|
||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
|
||||
|
||||
# Go 1.23 is the last release that requires Linux kernel version 2.6.32 or later. Go 1.24 will require Linux kernel version 3.2 or later.
|
||||
- { goos: linux, goarch: '386', output: '386-go123', goversion: '1.23' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go123, goversion: '1.23', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-go123, goversion: '1.23' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1-go123, goversion: '1.23', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2-go123, goversion: '1.23' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go123, goversion: '1.23' }
|
||||
|
||||
# only for test
|
||||
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-v1-go120, goversion: '1.20', test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v2, output: amd64-v2-go120, goversion: '1.20' }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-v3-go120, goversion: '1.20' }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -119,7 +152,7 @@ jobs:
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24'
|
||||
go-version: '1.25'
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
|
||||
@@ -134,6 +167,25 @@ jobs:
|
||||
sudo tar zxf go1.24.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.25.x
|
||||
# that means after golang1.26 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.25 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.24.x
|
||||
# that means after golang1.25 release it must be changed
|
||||
@@ -144,8 +196,9 @@ jobs:
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.24 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.24' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
@@ -164,6 +217,7 @@ jobs:
|
||||
- name: Revert Golang1.23 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
@@ -182,6 +236,7 @@ jobs:
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
@@ -192,16 +247,18 @@ jobs:
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
- name: Set variables
|
||||
run: |
|
||||
VERSION="${GITHUB_REF_NAME,,}-$(git rev-parse --short HEAD)"
|
||||
VERSION="${VERSION//\//-}"
|
||||
PackageVersion="$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | jq -r '.tag_name' | sed 's/v//g' | awk -F '.' '{$NF = $NF + 1; print}' OFS='.').${VERSION/-/.}"
|
||||
if [ -n "${{ github.event.inputs.version }}" ]; then
|
||||
VERSION=${{ github.event.inputs.version }}
|
||||
PackageVersion="${VERSION#v}" >> $GITHUB_ENV
|
||||
PackageVersion="${VERSION#v}"
|
||||
fi
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
echo "PackageVersion=${PackageVersion}" >> $GITHUB_ENV
|
||||
|
||||
24
.github/workflows/test.yml
vendored
24
.github/workflows/test.yml
vendored
@@ -24,6 +24,7 @@ jobs:
|
||||
- 'ubuntu-24.04-arm' # arm64 linux
|
||||
- 'macos-13' # amd64 macos
|
||||
go-version:
|
||||
- '1.25'
|
||||
- '1.24'
|
||||
- '1.23'
|
||||
- '1.22'
|
||||
@@ -47,6 +48,25 @@ jobs:
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.25.x
|
||||
# that means after golang1.26 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.25/
|
||||
# revert:
|
||||
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||
- name: Revert Golang1.25 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.25' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/8cb5472d94c34b88733a81091bd328e70ee565a4.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/6788c4c6f9fafb56729bad6b660f7ee2272d699f.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/a5b2168bb836ed9d6601c626f95e56c07923f906.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/f56f1e23507e646c85243a71bde7b9629b2f970c.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.24.x
|
||||
# that means after golang1.25 release it must be changed
|
||||
@@ -59,6 +79,7 @@ jobs:
|
||||
- name: Revert Golang1.24 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
@@ -77,6 +98,7 @@ jobs:
|
||||
- name: Revert Golang1.23 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||
@@ -95,6 +117,7 @@ jobs:
|
||||
- name: Revert Golang1.22 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||
@@ -105,6 +128,7 @@ jobs:
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }}
|
||||
run: |
|
||||
alias curl='curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}"'
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
|
||||
3
.github/workflows/trigger-cmfa-update.yml
vendored
3
.github/workflows/trigger-cmfa-update.yml
vendored
@@ -10,9 +10,6 @@ on:
|
||||
- Alpha
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request_target:
|
||||
branches:
|
||||
- Alpha
|
||||
|
||||
jobs:
|
||||
# Send "core-updated" to MetaCubeX/ClashMetaForAndroid to trigger update-dependencies
|
||||
|
||||
57
Makefile
57
Makefile
@@ -17,11 +17,19 @@ GOBUILD=CGO_ENABLED=0 go build -tags with_gvisor -trimpath -ldflags '-X "github.
|
||||
-w -s -buildid='
|
||||
|
||||
PLATFORM_LIST = \
|
||||
darwin-386 \
|
||||
darwin-amd64-compatible \
|
||||
darwin-amd64 \
|
||||
darwin-amd64-v1 \
|
||||
darwin-amd64-v2 \
|
||||
darwin-amd64-v3 \
|
||||
darwin-arm64 \
|
||||
linux-386 \
|
||||
linux-amd64-compatible \
|
||||
linux-amd64 \
|
||||
linux-amd64-v1 \
|
||||
linux-amd64-v2 \
|
||||
linux-amd64-v3 \
|
||||
linux-armv5 \
|
||||
linux-armv6 \
|
||||
linux-armv7 \
|
||||
@@ -43,37 +51,61 @@ WINDOWS_ARCH_LIST = \
|
||||
windows-386 \
|
||||
windows-amd64-compatible \
|
||||
windows-amd64 \
|
||||
windows-amd64-v1 \
|
||||
windows-amd64-v2 \
|
||||
windows-amd64-v3 \
|
||||
windows-arm64 \
|
||||
windows-arm32v7
|
||||
|
||||
all:linux-amd64 linux-arm64\
|
||||
darwin-amd64 darwin-arm64\
|
||||
windows-amd64 windows-arm64\
|
||||
all:linux-amd64-v3 linux-arm64\
|
||||
darwin-amd64-v3 darwin-arm64\
|
||||
windows-amd64-v3 windows-arm64\
|
||||
|
||||
|
||||
darwin-all: darwin-amd64 darwin-arm64
|
||||
darwin-all: darwin-amd64-v3 darwin-arm64
|
||||
|
||||
docker:
|
||||
GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
darwin-386:
|
||||
GOARCH=386 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-compatible:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-v1:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-v2:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-amd64-v3:
|
||||
GOARCH=amd64 GOOS=darwin GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
darwin-arm64:
|
||||
GOARCH=arm64 GOOS=darwin $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-386:
|
||||
GOARCH=386 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64-compatible:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64-compatible:
|
||||
linux-amd64-v1:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64-v2:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-amd64-v3:
|
||||
GOARCH=amd64 GOOS=linux GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
linux-arm64:
|
||||
GOARCH=arm64 GOOS=linux $(GOBUILD) -o $(BINDIR)/$(NAME)-$@
|
||||
|
||||
@@ -125,12 +157,21 @@ freebsd-arm64:
|
||||
windows-386:
|
||||
GOARCH=386 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64-compatible:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64-compatible:
|
||||
windows-amd64-v1:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v1 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64-v2:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v2 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-amd64-v3:
|
||||
GOARCH=amd64 GOOS=windows GOAMD64=v3 $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
windows-arm64:
|
||||
GOARCH=arm64 GOOS=windows $(GOBUILD) -o $(BINDIR)/$(NAME)-$@.exe
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ import (
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/queue"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/common/xsync"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
var UnifiedDelay = atomic.NewBool(false)
|
||||
@@ -35,7 +35,7 @@ type Proxy struct {
|
||||
C.ProxyAdapter
|
||||
alive atomic.Bool
|
||||
history *queue.Queue[C.DelayHistory]
|
||||
extra *xsync.MapOf[string, *internalProxyState]
|
||||
extra xsync.Map[string, *internalProxyState]
|
||||
}
|
||||
|
||||
// Adapter implements C.Proxy
|
||||
@@ -293,7 +293,7 @@ func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
ProxyAdapter: adapter,
|
||||
history: queue.New[C.DelayHistory](defaultHistoriesNum),
|
||||
alive: atomic.NewBool(true),
|
||||
extra: xsync.NewMapOf[string, *internalProxyState]()}
|
||||
}
|
||||
}
|
||||
|
||||
func urlToMetadata(rawURL string) (addr C.Metadata, err error) {
|
||||
|
||||
@@ -223,7 +223,7 @@ type conn struct {
|
||||
func (c *conn) RemoteDestination() string {
|
||||
if remoteAddr := c.RemoteAddr(); remoteAddr != nil {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(remoteAddr); err != nil {
|
||||
if err := m.SetRemoteAddr(remoteAddr); err == nil {
|
||||
if m.Valid() {
|
||||
return m.String()
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
} else {
|
||||
tlsConfig.NextProtos = []string{DefaultALPN}
|
||||
|
||||
@@ -153,7 +153,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(option.ALPN) > 0 {
|
||||
if option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
tlsConfig.NextProtos = option.ALPN
|
||||
}
|
||||
|
||||
|
||||
@@ -28,15 +28,16 @@ type Mieru struct {
|
||||
|
||||
type MieruOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
HandshakeMode string `proxy:"handshake-mode,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@@ -245,6 +246,9 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||
}
|
||||
}
|
||||
if handshakeMode, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; ok {
|
||||
config.Profile.HandshakeMode = (*mierupb.HandshakeMode)(&handshakeMode)
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
@@ -294,6 +298,11 @@ func validateMieruOption(option MieruOption) error {
|
||||
return fmt.Errorf("invalid multiplexing level: %s", option.Multiplexing)
|
||||
}
|
||||
}
|
||||
if option.HandshakeMode != "" {
|
||||
if _, ok := mierupb.HandshakeMode_value[option.HandshakeMode]; !ok {
|
||||
return fmt.Errorf("invalid handshake mode: %s", option.HandshakeMode)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
}
|
||||
|
||||
alpn := trojan.DefaultWebsocketALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
alpn = t.option.ALPN
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
// default tcp network
|
||||
// handle TLS
|
||||
alpn := trojan.DefaultALPN
|
||||
if len(t.option.ALPN) != 0 {
|
||||
if t.option.ALPN != nil { // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
alpn = t.option.ALPN
|
||||
}
|
||||
c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
|
||||
|
||||
@@ -3,14 +3,10 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
@@ -23,6 +19,7 @@ import (
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
vmessSing "github.com/metacubex/sing-vmess"
|
||||
@@ -30,16 +27,13 @@ import (
|
||||
M "github.com/metacubex/sing/common/metadata"
|
||||
)
|
||||
|
||||
const (
|
||||
// max packet length
|
||||
maxLength = 1024 << 3
|
||||
)
|
||||
|
||||
type Vless struct {
|
||||
*Base
|
||||
client *vless.Client
|
||||
option *VlessOption
|
||||
|
||||
encryption *encryption.ClientInstance
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
gunConfig *gun.Config
|
||||
@@ -62,6 +56,7 @@ type VlessOption struct {
|
||||
PacketAddr bool `proxy:"packet-addr,omitempty"`
|
||||
XUDP bool `proxy:"xudp,omitempty"`
|
||||
PacketEncoding string `proxy:"packet-encoding,omitempty"`
|
||||
Encryption string `proxy:"encryption,omitempty"`
|
||||
Network string `proxy:"network,omitempty"`
|
||||
ECHOpts ECHOptions `proxy:"ech-opts,omitempty"`
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
@@ -173,6 +168,12 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
done := N.SetupContextForConn(ctx, c)
|
||||
defer done(&err)
|
||||
}
|
||||
if v.encryption != nil {
|
||||
c, err = v.encryption.Handshake(c)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if metadata.NetWork == C.UDP {
|
||||
if v.option.PacketAddr {
|
||||
metadata = &C.Metadata{
|
||||
@@ -188,9 +189,6 @@ func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
}
|
||||
}
|
||||
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||
if v.option.PacketAddr {
|
||||
conn = packetaddr.NewBindConn(conn)
|
||||
}
|
||||
} else {
|
||||
conn, err = v.client.StreamConn(c, parseVlessAddr(metadata, false))
|
||||
}
|
||||
@@ -352,12 +350,11 @@ func (v *Vless) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metada
|
||||
), v), nil
|
||||
} else if v.option.PacketAddr {
|
||||
return newPacketConn(N.NewThreadSafePacketConn(
|
||||
packetaddr.NewConn(&vlessPacketConn{
|
||||
Conn: c, rAddr: metadata.UDPAddr(),
|
||||
}, M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
packetaddr.NewConn(v.client.PacketConn(c, metadata.UDPAddr()),
|
||||
M.SocksaddrFromNet(metadata.UDPAddr())),
|
||||
), v), nil
|
||||
}
|
||||
return newPacketConn(N.NewThreadSafePacketConn(&vlessPacketConn{Conn: c, rAddr: metadata.UDPAddr()}), v), nil
|
||||
return newPacketConn(N.NewThreadSafePacketConn(v.client.PacketConn(c, metadata.UDPAddr())), v), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
@@ -408,98 +405,6 @@ func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
}
|
||||
}
|
||||
|
||||
type vlessPacketConn struct {
|
||||
net.Conn
|
||||
rAddr net.Addr
|
||||
remain int
|
||||
mux sync.Mutex
|
||||
cache [2]byte
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) writePacket(payload []byte) (int, error) {
|
||||
binary.BigEndian.PutUint16(c.cache[:], uint16(len(payload)))
|
||||
|
||||
if _, err := c.Conn.Write(c.cache[:]); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return c.Conn.Write(payload)
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) WriteTo(b []byte, addr net.Addr) (int, error) {
|
||||
total := len(b)
|
||||
if total == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if total <= maxLength {
|
||||
return c.writePacket(b)
|
||||
}
|
||||
|
||||
offset := 0
|
||||
|
||||
for offset < total {
|
||||
cursor := offset + maxLength
|
||||
if cursor > total {
|
||||
cursor = total
|
||||
}
|
||||
|
||||
n, err := c.writePacket(b[offset:cursor])
|
||||
if err != nil {
|
||||
return offset + n, err
|
||||
}
|
||||
|
||||
offset = cursor
|
||||
if offset == total {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return total, nil
|
||||
}
|
||||
|
||||
func (c *vlessPacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if c.remain > 0 {
|
||||
length := len(b)
|
||||
if c.remain < length {
|
||||
length = c.remain
|
||||
}
|
||||
|
||||
n, err := c.Conn.Read(b[:length])
|
||||
if err != nil {
|
||||
return 0, c.rAddr, err
|
||||
}
|
||||
|
||||
c.remain -= n
|
||||
return n, c.rAddr, nil
|
||||
}
|
||||
|
||||
if _, err := c.Conn.Read(b[:2]); err != nil {
|
||||
return 0, c.rAddr, err
|
||||
}
|
||||
|
||||
total := int(binary.BigEndian.Uint16(b[:2]))
|
||||
if total == 0 {
|
||||
return 0, c.rAddr, nil
|
||||
}
|
||||
|
||||
length := len(b)
|
||||
if length > total {
|
||||
length = total
|
||||
}
|
||||
|
||||
if _, err := io.ReadFull(c.Conn, b[:length]); err != nil {
|
||||
return 0, c.rAddr, errors.New("read packet error")
|
||||
}
|
||||
|
||||
c.remain = total - length
|
||||
|
||||
return length, c.rAddr, nil
|
||||
}
|
||||
|
||||
func NewVless(option VlessOption) (*Vless, error) {
|
||||
var addons *vless.Addons
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
@@ -547,6 +452,11 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
option: &option,
|
||||
}
|
||||
|
||||
v.encryption, err = encryption.NewClient(option.Encryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -87,15 +87,26 @@ type WireGuardPeerOption struct {
|
||||
}
|
||||
|
||||
type AmneziaWGOption struct {
|
||||
JC int `proxy:"jc"`
|
||||
JMin int `proxy:"jmin"`
|
||||
JMax int `proxy:"jmax"`
|
||||
S1 int `proxy:"s1"`
|
||||
S2 int `proxy:"s2"`
|
||||
H1 uint32 `proxy:"h1"`
|
||||
H2 uint32 `proxy:"h2"`
|
||||
H3 uint32 `proxy:"h3"`
|
||||
H4 uint32 `proxy:"h4"`
|
||||
JC int `proxy:"jc,omitempty"`
|
||||
JMin int `proxy:"jmin,omitempty"`
|
||||
JMax int `proxy:"jmax,omitempty"`
|
||||
S1 int `proxy:"s1,omitempty"`
|
||||
S2 int `proxy:"s2,omitempty"`
|
||||
H1 uint32 `proxy:"h1,omitempty"`
|
||||
H2 uint32 `proxy:"h2,omitempty"`
|
||||
H3 uint32 `proxy:"h3,omitempty"`
|
||||
H4 uint32 `proxy:"h4,omitempty"`
|
||||
|
||||
// AmneziaWG v1.5
|
||||
I1 string `proxy:"i1,omitempty"`
|
||||
I2 string `proxy:"i2,omitempty"`
|
||||
I3 string `proxy:"i3,omitempty"`
|
||||
I4 string `proxy:"i4,omitempty"`
|
||||
I5 string `proxy:"i5,omitempty"`
|
||||
J1 string `proxy:"j1,omitempty"`
|
||||
J2 string `proxy:"j2,omitempty"`
|
||||
J3 string `proxy:"j3,omitempty"`
|
||||
Itime int64 `proxy:"itime,omitempty"`
|
||||
}
|
||||
|
||||
type wgSingErrorHandler struct {
|
||||
@@ -386,15 +397,60 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||
if !updateOnly {
|
||||
ipcConf += "private_key=" + w.option.PrivateKey + "\n"
|
||||
if w.option.AmneziaWGOption != nil {
|
||||
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
|
||||
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
|
||||
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
|
||||
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
if w.option.AmneziaWGOption.JC != 0 {
|
||||
ipcConf += "jc=" + strconv.Itoa(w.option.AmneziaWGOption.JC) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.JMin != 0 {
|
||||
ipcConf += "jmin=" + strconv.Itoa(w.option.AmneziaWGOption.JMin) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.JMax != 0 {
|
||||
ipcConf += "jmax=" + strconv.Itoa(w.option.AmneziaWGOption.JMax) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.S1 != 0 {
|
||||
ipcConf += "s1=" + strconv.Itoa(w.option.AmneziaWGOption.S1) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.S2 != 0 {
|
||||
ipcConf += "s2=" + strconv.Itoa(w.option.AmneziaWGOption.S2) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H1 != 0 {
|
||||
ipcConf += "h1=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H1), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H2 != 0 {
|
||||
ipcConf += "h2=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H2), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H3 != 0 {
|
||||
ipcConf += "h3=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H3), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.H4 != 0 {
|
||||
ipcConf += "h4=" + strconv.FormatUint(uint64(w.option.AmneziaWGOption.H4), 10) + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I1 != "" {
|
||||
ipcConf += "i1=" + w.option.AmneziaWGOption.I1 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I2 != "" {
|
||||
ipcConf += "i2=" + w.option.AmneziaWGOption.I2 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I3 != "" {
|
||||
ipcConf += "i3=" + w.option.AmneziaWGOption.I3 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I4 != "" {
|
||||
ipcConf += "i4=" + w.option.AmneziaWGOption.I4 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.I5 != "" {
|
||||
ipcConf += "i5=" + w.option.AmneziaWGOption.I5 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J1 != "" {
|
||||
ipcConf += "j1=" + w.option.AmneziaWGOption.J1 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J2 != "" {
|
||||
ipcConf += "j2=" + w.option.AmneziaWGOption.J2 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.J3 != "" {
|
||||
ipcConf += "j3=" + w.option.AmneziaWGOption.J3 + "\n"
|
||||
}
|
||||
if w.option.AmneziaWGOption.Itime != 0 {
|
||||
ipcConf += "itime=" + strconv.FormatInt(int64(w.option.AmneziaWGOption.Itime), 10) + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(w.option.Peers) > 0 {
|
||||
|
||||
@@ -108,6 +108,9 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, C.Path.ErrNotSafePath(path)
|
||||
}
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
|
||||
63
common/atomic/enum.go
Normal file
63
common/atomic/enum.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type Int32Enum[T ~int32] struct {
|
||||
value atomic.Int32
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(i.Load())
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) UnmarshalJSON(b []byte) error {
|
||||
var v T
|
||||
if err := json.Unmarshal(b, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v T
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) String() string {
|
||||
return fmt.Sprint(i.Load())
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) Store(v T) {
|
||||
i.value.Store(int32(v))
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) Load() T {
|
||||
return T(i.value.Load())
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) Swap(new T) T {
|
||||
return T(i.value.Swap(int32(new)))
|
||||
}
|
||||
|
||||
func (i *Int32Enum[T]) CompareAndSwap(old, new T) bool {
|
||||
return i.value.CompareAndSwap(int32(old), int32(new))
|
||||
}
|
||||
|
||||
func NewInt32Enum[T ~int32](v T) *Int32Enum[T] {
|
||||
a := &Int32Enum[T]{}
|
||||
a.Store(v)
|
||||
return a
|
||||
}
|
||||
@@ -29,6 +29,19 @@ func (i *Bool) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Bool) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Bool) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v bool
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Bool) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatBool(v)
|
||||
@@ -58,6 +71,19 @@ func (p *Pointer[T]) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pointer[T]) MarshalYAML() (any, error) {
|
||||
return p.Load(), nil
|
||||
}
|
||||
|
||||
func (p *Pointer[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v *T
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pointer[T]) String() string {
|
||||
return fmt.Sprint(p.Load())
|
||||
}
|
||||
@@ -84,6 +110,19 @@ func (i *Int32) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int32) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Int32) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v int32
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int32) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
@@ -111,6 +150,19 @@ func (i *Int64) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int64) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Int64) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v int64
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Int64) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
@@ -138,6 +190,19 @@ func (i *Uint32) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uint32) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Uint32) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v uint32
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uint32) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
@@ -165,6 +230,19 @@ func (i *Uint64) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uint64) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Uint64) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v uint64
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uint64) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
@@ -192,6 +270,19 @@ func (i *Uintptr) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uintptr) MarshalYAML() (any, error) {
|
||||
return i.Load(), nil
|
||||
}
|
||||
|
||||
func (i *Uintptr) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v uintptr
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
i.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Uintptr) String() string {
|
||||
v := i.Load()
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
|
||||
@@ -5,49 +5,50 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func DefaultValue[T any]() T {
|
||||
var defaultValue T
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
type TypedValue[T any] struct {
|
||||
_ noCopy
|
||||
value atomic.Value
|
||||
value atomic.Pointer[T]
|
||||
}
|
||||
|
||||
// tValue is a struct with determined type to resolve atomic.Value usages with interface types
|
||||
// https://github.com/golang/go/issues/22550
|
||||
//
|
||||
// The intention to have an atomic value store for errors. However, running this code panics:
|
||||
// panic: sync/atomic: store of inconsistently typed value into Value
|
||||
// This is because atomic.Value requires that the underlying concrete type be the same (which is a reasonable expectation for its implementation).
|
||||
// When going through the atomic.Value.Store method call, the fact that both these are of the error interface is lost.
|
||||
type tValue[T any] struct {
|
||||
value T
|
||||
func (t *TypedValue[T]) Load() (v T) {
|
||||
v, _ = t.LoadOk()
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Load() T {
|
||||
func (t *TypedValue[T]) LoadOk() (v T, ok bool) {
|
||||
value := t.value.Load()
|
||||
if value == nil {
|
||||
return DefaultValue[T]()
|
||||
return
|
||||
}
|
||||
return value.(tValue[T]).value
|
||||
return *value, true
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Store(value T) {
|
||||
t.value.Store(tValue[T]{value})
|
||||
t.value.Store(&value)
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) Swap(new T) T {
|
||||
old := t.value.Swap(tValue[T]{new})
|
||||
func (t *TypedValue[T]) Swap(new T) (v T) {
|
||||
old := t.value.Swap(&new)
|
||||
if old == nil {
|
||||
return DefaultValue[T]()
|
||||
return
|
||||
}
|
||||
return old.(tValue[T]).value
|
||||
return *old
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) CompareAndSwap(old, new T) bool {
|
||||
return t.value.CompareAndSwap(tValue[T]{old}, tValue[T]{new})
|
||||
for {
|
||||
currentP := t.value.Load()
|
||||
var currentValue T
|
||||
if currentP != nil {
|
||||
currentValue = *currentP
|
||||
}
|
||||
// Compare old and current via runtime equality check.
|
||||
if any(currentValue) != any(old) {
|
||||
return false
|
||||
}
|
||||
if t.value.CompareAndSwap(currentP, &new) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) MarshalJSON() ([]byte, error) {
|
||||
@@ -63,13 +64,20 @@ func (t *TypedValue[T]) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) MarshalYAML() (any, error) {
|
||||
return t.Load(), nil
|
||||
}
|
||||
|
||||
func (t *TypedValue[T]) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var v T
|
||||
if err := unmarshal(&v); err != nil {
|
||||
return err
|
||||
}
|
||||
t.Store(v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTypedValue[T any](t T) (v TypedValue[T]) {
|
||||
v.Store(t)
|
||||
return
|
||||
}
|
||||
|
||||
type noCopy struct{}
|
||||
|
||||
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||
func (*noCopy) Lock() {}
|
||||
func (*noCopy) Unlock() {}
|
||||
|
||||
169
common/atomic/value_test.go
Normal file
169
common/atomic/value_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypedValue(t *testing.T) {
|
||||
{
|
||||
var v TypedValue[int]
|
||||
got, gotOk := v.LoadOk()
|
||||
if got != 0 || gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (0, false)", got, gotOk)
|
||||
}
|
||||
v.Store(1)
|
||||
got, gotOk = v.LoadOk()
|
||||
if got != 1 || !gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (1, true)", got, gotOk)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var v TypedValue[error]
|
||||
got, gotOk := v.LoadOk()
|
||||
if got != nil || gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (nil, false)", got, gotOk)
|
||||
}
|
||||
v.Store(io.EOF)
|
||||
got, gotOk = v.LoadOk()
|
||||
if got != io.EOF || !gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (EOF, true)", got, gotOk)
|
||||
}
|
||||
err := &os.PathError{}
|
||||
v.Store(err)
|
||||
got, gotOk = v.LoadOk()
|
||||
if got != err || !gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (%v, true)", got, gotOk, err)
|
||||
}
|
||||
v.Store(nil)
|
||||
got, gotOk = v.LoadOk()
|
||||
if got != nil || !gotOk {
|
||||
t.Fatalf("LoadOk = (%v, %v), want (nil, true)", got, gotOk)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
e1, e2, e3 := io.EOF, &os.PathError{}, &os.PathError{}
|
||||
var v TypedValue[error]
|
||||
if v.CompareAndSwap(e1, e2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, e1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != e1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e1)
|
||||
}
|
||||
if v.CompareAndSwap(e2, e3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e1)
|
||||
}
|
||||
if v.CompareAndSwap(e1, e2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
if v.CompareAndSwap(e3, e2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, e3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != e2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, e2)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
c1, c2, c3 := make(chan struct{}), make(chan struct{}), make(chan struct{})
|
||||
var v TypedValue[chan struct{}]
|
||||
if v.CompareAndSwap(c1, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c2, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c1, c2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(c3, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
c1, c2, c3 := &io.LimitedReader{}, &io.SectionReader{}, &io.SectionReader{}
|
||||
var v TypedValue[io.Reader]
|
||||
if v.CompareAndSwap(c1, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != nil {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, nil)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c1) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c2, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c1 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c1)
|
||||
}
|
||||
if v.CompareAndSwap(c1, c2) != true {
|
||||
t.Fatalf("CompareAndSwap = false, want true")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(c3, c2) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
if v.CompareAndSwap(nil, c3) != false {
|
||||
t.Fatalf("CompareAndSwap = true, want false")
|
||||
}
|
||||
if value := v.Load(); value != c2 {
|
||||
t.Fatalf("Load = (%v), want (%v)", value, c2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ var NewPacket = buf.NewPacket
|
||||
var NewSize = buf.NewSize
|
||||
var With = buf.With
|
||||
var As = buf.As
|
||||
var ReleaseMulti = buf.ReleaseMulti
|
||||
|
||||
var (
|
||||
Must = common.Must
|
||||
|
||||
@@ -2,6 +2,7 @@ package convert
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -43,3 +44,22 @@ func decodeUrlSafe(data string) string {
|
||||
}
|
||||
return string(dcBuf)
|
||||
}
|
||||
|
||||
func TryDecodeBase64(s string) (decoded []byte, err error) {
|
||||
if len(s)%4 == 0 {
|
||||
if decoded, err = base64.StdEncoding.DecodeString(s); err == nil {
|
||||
return
|
||||
}
|
||||
if decoded, err = base64.URLEncoding.DecodeString(s); err == nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if decoded, err = base64.RawStdEncoding.DecodeString(s); err == nil {
|
||||
return
|
||||
}
|
||||
if decoded, err = base64.RawURLEncoding.DecodeString(s); err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("invalid base64-encoded string")
|
||||
}
|
||||
|
||||
@@ -208,6 +208,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if decodedHost, err := tryDecodeBase64([]byte(urlVLess.Host)); err == nil {
|
||||
urlVLess.Host = string(decodedHost)
|
||||
}
|
||||
query := urlVLess.Query()
|
||||
vless := make(map[string]any, 20)
|
||||
err = handleVShareLink(names, urlVLess, scheme, vless)
|
||||
@@ -218,6 +221,9 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
if flow := query.Get("flow"); flow != "" {
|
||||
vless["flow"] = strings.ToLower(flow)
|
||||
}
|
||||
if encryption := query.Get("encryption"); encryption != "" {
|
||||
vless["encryption"] = encryption
|
||||
}
|
||||
proxies = append(proxies, vless)
|
||||
|
||||
case "vmess":
|
||||
@@ -456,12 +462,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
proxies = append(proxies, ss)
|
||||
|
||||
case "ssr":
|
||||
dcBuf, err := encRaw.DecodeString(body)
|
||||
dcBuf, err := TryDecodeBase64(body)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64&protoparam=&remarks=urlsafebase64&group=urlsafebase64&udpport=0&uot=1
|
||||
// ssr://host:port:protocol:method:obfs:urlsafebase64pass/?obfsparam=urlsafebase64param&protoparam=urlsafebase64param&remarks=urlsafebase64remarks&group=urlsafebase64group&udpport=0&uot=1
|
||||
|
||||
before, after, ok := strings.Cut(string(dcBuf), "/?")
|
||||
if !ok {
|
||||
@@ -490,7 +496,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
name := uniqueName(names, remarks)
|
||||
|
||||
obfsParam := decodeUrlSafe(query.Get("obfsparam"))
|
||||
protocolParam := query.Get("protoparam")
|
||||
protocolParam := decodeUrlSafe(query.Get("protoparam"))
|
||||
|
||||
ssr := make(map[string]any, 20)
|
||||
|
||||
@@ -513,6 +519,101 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
}
|
||||
|
||||
proxies = append(proxies, ssr)
|
||||
|
||||
case "socks", "socks5", "socks5h", "http", "https":
|
||||
link, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
server := link.Hostname()
|
||||
if server == "" {
|
||||
continue
|
||||
}
|
||||
portStr := link.Port()
|
||||
if portStr == "" {
|
||||
continue
|
||||
}
|
||||
remarks := link.Fragment
|
||||
if remarks == "" {
|
||||
remarks = fmt.Sprintf("%s:%s", server, portStr)
|
||||
}
|
||||
name := uniqueName(names, remarks)
|
||||
encodeStr := link.User.String()
|
||||
var username, password string
|
||||
if encodeStr != "" {
|
||||
decodeStr := string(DecodeBase64([]byte(encodeStr)))
|
||||
splitStr := strings.Split(decodeStr, ":")
|
||||
|
||||
// todo: should use url.QueryUnescape ?
|
||||
username = splitStr[0]
|
||||
if len(splitStr) == 2 {
|
||||
password = splitStr[1]
|
||||
}
|
||||
}
|
||||
socks := make(map[string]any, 10)
|
||||
socks["name"] = name
|
||||
socks["type"] = func() string {
|
||||
switch scheme {
|
||||
case "socks", "socks5", "socks5h":
|
||||
return "socks5"
|
||||
case "http", "https":
|
||||
return "http"
|
||||
}
|
||||
return scheme
|
||||
}()
|
||||
socks["server"] = server
|
||||
socks["port"] = portStr
|
||||
socks["username"] = username
|
||||
socks["password"] = password
|
||||
socks["skip-cert-verify"] = true
|
||||
if scheme == "https" {
|
||||
socks["tls"] = true
|
||||
}
|
||||
|
||||
proxies = append(proxies, socks)
|
||||
|
||||
case "anytls":
|
||||
// https://github.com/anytls/anytls-go/blob/main/docs/uri_scheme.md
|
||||
link, err := url.Parse(line)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
username := link.User.Username()
|
||||
password, exist := link.User.Password()
|
||||
if !exist {
|
||||
password = username
|
||||
}
|
||||
query := link.Query()
|
||||
server := link.Hostname()
|
||||
if server == "" {
|
||||
continue
|
||||
}
|
||||
portStr := link.Port()
|
||||
if portStr == "" {
|
||||
continue
|
||||
}
|
||||
insecure, sni := query.Get("insecure"), query.Get("sni")
|
||||
insecureBool := insecure == "1"
|
||||
fingerprint := query.Get("hpkp")
|
||||
|
||||
remarks := link.Fragment
|
||||
if remarks == "" {
|
||||
remarks = fmt.Sprintf("%s:%s", server, portStr)
|
||||
}
|
||||
name := uniqueName(names, remarks)
|
||||
anytls := make(map[string]any, 10)
|
||||
anytls["name"] = name
|
||||
anytls["type"] = "anytls"
|
||||
anytls["server"] = server
|
||||
anytls["port"] = portStr
|
||||
anytls["username"] = username
|
||||
anytls["password"] = password
|
||||
anytls["sni"] = sni
|
||||
anytls["fingerprint"] = fingerprint
|
||||
anytls["skip-cert-verify"] = insecureBool
|
||||
anytls["udp"] = true
|
||||
|
||||
proxies = append(proxies, anytls)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
common/maphash/common.go
Normal file
19
common/maphash/common.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package maphash
|
||||
|
||||
import "hash/maphash"
|
||||
|
||||
type Seed = maphash.Seed
|
||||
|
||||
func MakeSeed() Seed {
|
||||
return maphash.MakeSeed()
|
||||
}
|
||||
|
||||
type Hash = maphash.Hash
|
||||
|
||||
func Bytes(seed Seed, b []byte) uint64 {
|
||||
return maphash.Bytes(seed, b)
|
||||
}
|
||||
|
||||
func String(seed Seed, s string) uint64 {
|
||||
return maphash.String(seed, s)
|
||||
}
|
||||
140
common/maphash/comparable_go120.go
Normal file
140
common/maphash/comparable_go120.go
Normal file
@@ -0,0 +1,140 @@
|
||||
//go:build !go1.24
|
||||
|
||||
package maphash
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func Comparable[T comparable](s Seed, v T) uint64 {
|
||||
return comparableHash(*(*seedTyp)(unsafe.Pointer(&s)), v)
|
||||
}
|
||||
|
||||
func comparableHash[T comparable](seed seedTyp, v T) uint64 {
|
||||
s := seed.s
|
||||
var m map[T]struct{}
|
||||
mTyp := iTypeOf(m)
|
||||
var hasher func(unsafe.Pointer, uintptr) uintptr
|
||||
hasher = (*iMapType)(unsafe.Pointer(mTyp)).Hasher
|
||||
|
||||
p := escape(unsafe.Pointer(&v))
|
||||
|
||||
if ptrSize == 8 {
|
||||
return uint64(hasher(p, uintptr(s)))
|
||||
}
|
||||
lo := hasher(p, uintptr(s))
|
||||
hi := hasher(p, uintptr(s>>32))
|
||||
return uint64(hi)<<32 | uint64(lo)
|
||||
}
|
||||
|
||||
// WriteComparable adds x to the data hashed by h.
|
||||
func WriteComparable[T comparable](h *Hash, x T) {
|
||||
// writeComparable (not in purego mode) directly operates on h.state
|
||||
// without using h.buf. Mix in the buffer length so it won't
|
||||
// commute with a buffered write, which either changes h.n or changes
|
||||
// h.state.
|
||||
hash := (*hashTyp)(unsafe.Pointer(h))
|
||||
if hash.n != 0 {
|
||||
hash.state.s = comparableHash(hash.state, hash.n)
|
||||
}
|
||||
hash.state.s = comparableHash(hash.state, x)
|
||||
}
|
||||
|
||||
// go/src/hash/maphash/maphash.go
|
||||
type hashTyp struct {
|
||||
_ [0]func() // not comparable
|
||||
seed seedTyp // initial seed used for this hash
|
||||
state seedTyp // current hash of all flushed bytes
|
||||
buf [128]byte // unflushed byte buffer
|
||||
n int // number of unflushed bytes
|
||||
}
|
||||
|
||||
type seedTyp struct {
|
||||
s uint64
|
||||
}
|
||||
|
||||
type iTFlag uint8
|
||||
type iKind uint8
|
||||
type iNameOff int32
|
||||
|
||||
// TypeOff is the offset to a type from moduledata.types. See resolveTypeOff in runtime.
|
||||
type iTypeOff int32
|
||||
|
||||
type iType struct {
|
||||
Size_ uintptr
|
||||
PtrBytes uintptr // number of (prefix) bytes in the type that can contain pointers
|
||||
Hash uint32 // hash of type; avoids computation in hash tables
|
||||
TFlag iTFlag // extra type information flags
|
||||
Align_ uint8 // alignment of variable with this type
|
||||
FieldAlign_ uint8 // alignment of struct field with this type
|
||||
Kind_ iKind // enumeration for C
|
||||
// function for comparing objects of this type
|
||||
// (ptr to object A, ptr to object B) -> ==?
|
||||
Equal func(unsafe.Pointer, unsafe.Pointer) bool
|
||||
// GCData stores the GC type data for the garbage collector.
|
||||
// Normally, GCData points to a bitmask that describes the
|
||||
// ptr/nonptr fields of the type. The bitmask will have at
|
||||
// least PtrBytes/ptrSize bits.
|
||||
// If the TFlagGCMaskOnDemand bit is set, GCData is instead a
|
||||
// **byte and the pointer to the bitmask is one dereference away.
|
||||
// The runtime will build the bitmask if needed.
|
||||
// (See runtime/type.go:getGCMask.)
|
||||
// Note: multiple types may have the same value of GCData,
|
||||
// including when TFlagGCMaskOnDemand is set. The types will, of course,
|
||||
// have the same pointer layout (but not necessarily the same size).
|
||||
GCData *byte
|
||||
Str iNameOff // string form
|
||||
PtrToThis iTypeOff // type for pointer to this type, may be zero
|
||||
}
|
||||
|
||||
type iMapType struct {
|
||||
iType
|
||||
Key *iType
|
||||
Elem *iType
|
||||
Group *iType // internal type representing a slot group
|
||||
// function for hashing keys (ptr to key, seed) -> hash
|
||||
Hasher func(unsafe.Pointer, uintptr) uintptr
|
||||
}
|
||||
|
||||
func iTypeOf(a any) *iType {
|
||||
eface := *(*iEmptyInterface)(unsafe.Pointer(&a))
|
||||
// Types are either static (for compiler-created types) or
|
||||
// heap-allocated but always reachable (for reflection-created
|
||||
// types, held in the central map). So there is no need to
|
||||
// escape types. noescape here help avoid unnecessary escape
|
||||
// of v.
|
||||
return (*iType)(noescape(unsafe.Pointer(eface.Type)))
|
||||
}
|
||||
|
||||
type iEmptyInterface struct {
|
||||
Type *iType
|
||||
Data unsafe.Pointer
|
||||
}
|
||||
|
||||
// noescape hides a pointer from escape analysis. noescape is
|
||||
// the identity function but escape analysis doesn't think the
|
||||
// output depends on the input. noescape is inlined and currently
|
||||
// compiles down to zero instructions.
|
||||
// USE CAREFULLY!
|
||||
//
|
||||
// nolint:all
|
||||
//
|
||||
//go:nosplit
|
||||
//goland:noinspection ALL
|
||||
func noescape(p unsafe.Pointer) unsafe.Pointer {
|
||||
x := uintptr(p)
|
||||
return unsafe.Pointer(x ^ 0)
|
||||
}
|
||||
|
||||
var alwaysFalse bool
|
||||
var escapeSink any
|
||||
|
||||
// escape forces any pointers in x to escape to the heap.
|
||||
func escape[T any](x T) T {
|
||||
if alwaysFalse {
|
||||
escapeSink = x
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ptrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0)) but as an ideal constant.
|
||||
// It is also the size of the machine's native word size (that is, 4 on 32-bit systems, 8 on 64-bit).
|
||||
const ptrSize = 4 << (^uintptr(0) >> 63)
|
||||
13
common/maphash/comparable_go124.go
Normal file
13
common/maphash/comparable_go124.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build go1.24
|
||||
|
||||
package maphash
|
||||
|
||||
import "hash/maphash"
|
||||
|
||||
func Comparable[T comparable](seed Seed, v T) uint64 {
|
||||
return maphash.Comparable(seed, v)
|
||||
}
|
||||
|
||||
func WriteComparable[T comparable](h *Hash, x T) {
|
||||
maphash.WriteComparable(h, x)
|
||||
}
|
||||
532
common/maphash/maphash_test.go
Normal file
532
common/maphash/maphash_test.go
Normal file
@@ -0,0 +1,532 @@
|
||||
// Copyright 2019 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package maphash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
rand "github.com/metacubex/randv2"
|
||||
)
|
||||
|
||||
func TestUnseededHash(t *testing.T) {
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
h := new(Hash)
|
||||
m[h.Sum64()] = struct{}{}
|
||||
}
|
||||
if len(m) < 900 {
|
||||
t.Errorf("empty hash not sufficiently random: got %d, want 1000", len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeededHash(t *testing.T) {
|
||||
s := MakeSeed()
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
h := new(Hash)
|
||||
h.SetSeed(s)
|
||||
m[h.Sum64()] = struct{}{}
|
||||
}
|
||||
if len(m) != 1 {
|
||||
t.Errorf("seeded hash is random: got %d, want 1", len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashGrouping(t *testing.T) {
|
||||
b := bytes.Repeat([]byte("foo"), 100)
|
||||
hh := make([]*Hash, 7)
|
||||
for i := range hh {
|
||||
hh[i] = new(Hash)
|
||||
}
|
||||
for _, h := range hh[1:] {
|
||||
h.SetSeed(hh[0].Seed())
|
||||
}
|
||||
hh[0].Write(b)
|
||||
hh[1].WriteString(string(b))
|
||||
|
||||
writeByte := func(h *Hash, b byte) {
|
||||
err := h.WriteByte(b)
|
||||
if err != nil {
|
||||
t.Fatalf("WriteByte: %v", err)
|
||||
}
|
||||
}
|
||||
writeSingleByte := func(h *Hash, b byte) {
|
||||
_, err := h.Write([]byte{b})
|
||||
if err != nil {
|
||||
t.Fatalf("Write single byte: %v", err)
|
||||
}
|
||||
}
|
||||
writeStringSingleByte := func(h *Hash, b byte) {
|
||||
_, err := h.WriteString(string([]byte{b}))
|
||||
if err != nil {
|
||||
t.Fatalf("WriteString single byte: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for i, x := range b {
|
||||
writeByte(hh[2], x)
|
||||
writeSingleByte(hh[3], x)
|
||||
if i == 0 {
|
||||
writeByte(hh[4], x)
|
||||
} else {
|
||||
writeSingleByte(hh[4], x)
|
||||
}
|
||||
writeStringSingleByte(hh[5], x)
|
||||
if i == 0 {
|
||||
writeByte(hh[6], x)
|
||||
} else {
|
||||
writeStringSingleByte(hh[6], x)
|
||||
}
|
||||
}
|
||||
|
||||
sum := hh[0].Sum64()
|
||||
for i, h := range hh {
|
||||
if sum != h.Sum64() {
|
||||
t.Errorf("hash %d not identical to a single Write", i)
|
||||
}
|
||||
}
|
||||
|
||||
if sum1 := Bytes(hh[0].Seed(), b); sum1 != hh[0].Sum64() {
|
||||
t.Errorf("hash using Bytes not identical to a single Write")
|
||||
}
|
||||
|
||||
if sum1 := String(hh[0].Seed(), string(b)); sum1 != hh[0].Sum64() {
|
||||
t.Errorf("hash using String not identical to a single Write")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashBytesVsString(t *testing.T) {
|
||||
s := "foo"
|
||||
b := []byte(s)
|
||||
h1 := new(Hash)
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
n1, err1 := h1.WriteString(s)
|
||||
if n1 != len(s) || err1 != nil {
|
||||
t.Fatalf("WriteString(s) = %d, %v, want %d, nil", n1, err1, len(s))
|
||||
}
|
||||
n2, err2 := h2.Write(b)
|
||||
if n2 != len(b) || err2 != nil {
|
||||
t.Fatalf("Write(b) = %d, %v, want %d, nil", n2, err2, len(b))
|
||||
}
|
||||
if h1.Sum64() != h2.Sum64() {
|
||||
t.Errorf("hash of string and bytes not identical")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashHighBytes(t *testing.T) {
|
||||
// See issue 34925.
|
||||
const N = 10
|
||||
m := map[uint64]struct{}{}
|
||||
for i := 0; i < N; i++ {
|
||||
h := new(Hash)
|
||||
h.WriteString("foo")
|
||||
m[h.Sum64()>>32] = struct{}{}
|
||||
}
|
||||
if len(m) < N/2 {
|
||||
t.Errorf("from %d seeds, wanted at least %d different hashes; got %d", N, N/2, len(m))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepeat(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("testing")
|
||||
sum1 := h1.Sum64()
|
||||
|
||||
h1.Reset()
|
||||
h1.WriteString("testing")
|
||||
sum2 := h1.Sum64()
|
||||
|
||||
if sum1 != sum2 {
|
||||
t.Errorf("different sum after resetting: %#x != %#x", sum1, sum2)
|
||||
}
|
||||
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("testing")
|
||||
sum3 := h2.Sum64()
|
||||
|
||||
if sum1 != sum3 {
|
||||
t.Errorf("different sum on the same seed: %#x != %#x", sum1, sum3)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromSum64(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
x := h1.Sum64() // seed generated here
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromSeed(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
_ = h1.Seed() // seed generated here
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromFlush(t *testing.T) {
|
||||
b := make([]byte, 65)
|
||||
h1 := new(Hash)
|
||||
h1.Write(b) // seed generated here
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.Write(b)
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSeedFromReset(t *testing.T) {
|
||||
h1 := new(Hash)
|
||||
h1.WriteString("foo")
|
||||
h1.Reset() // seed generated here
|
||||
h1.WriteString("foo")
|
||||
x := h1.Sum64()
|
||||
h2 := new(Hash)
|
||||
h2.SetSeed(h1.Seed())
|
||||
h2.WriteString("foo")
|
||||
y := h2.Sum64()
|
||||
if x != y {
|
||||
t.Errorf("hashes don't match: want %x, got %x", x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func negativeZero[T float32 | float64]() T {
|
||||
var f T
|
||||
f = -f
|
||||
return f
|
||||
}
|
||||
|
||||
func TestComparable(t *testing.T) {
|
||||
testComparable(t, int64(2))
|
||||
testComparable(t, uint64(8))
|
||||
testComparable(t, uintptr(12))
|
||||
testComparable(t, any("s"))
|
||||
testComparable(t, "s")
|
||||
testComparable(t, true)
|
||||
testComparable(t, new(float64))
|
||||
testComparable(t, float64(9))
|
||||
testComparable(t, complex128(9i+1))
|
||||
testComparable(t, struct{}{})
|
||||
testComparable(t, struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
type S struct {
|
||||
s string
|
||||
}
|
||||
s1 := S{s: heapStr(t)}
|
||||
s2 := S{s: heapStr(t)}
|
||||
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
||||
t.Fatalf("unexpected two heapStr ptr equal")
|
||||
}
|
||||
if s1.s != s2.s {
|
||||
t.Fatalf("unexpected two heapStr value not equal")
|
||||
}
|
||||
testComparable(t, s1, s2)
|
||||
testComparable(t, s1.s, s2.s)
|
||||
testComparable(t, float32(0), negativeZero[float32]())
|
||||
testComparable(t, float64(0), negativeZero[float64]())
|
||||
testComparableNoEqual(t, math.NaN(), math.NaN())
|
||||
testComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
||||
testComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
||||
testComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
||||
}
|
||||
|
||||
func testComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
||||
seed := MakeSeed()
|
||||
if Comparable(seed, v1) == Comparable(seed, v2) {
|
||||
t.Fatalf("Comparable(seed, %v) == Comparable(seed, %v)", v1, v2)
|
||||
}
|
||||
}
|
||||
|
||||
var heapStrValue = []byte("aTestString")
|
||||
|
||||
func heapStr(t *testing.T) string {
|
||||
return string(heapStrValue)
|
||||
}
|
||||
|
||||
func testComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
||||
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
||||
var a, b T = v, v
|
||||
if len(v2) != 0 {
|
||||
b = v2[0]
|
||||
}
|
||||
var pa *T = &a
|
||||
seed := MakeSeed()
|
||||
if Comparable(seed, a) != Comparable(seed, b) {
|
||||
t.Fatalf("Comparable(seed, %v) != Comparable(seed, %v)", a, b)
|
||||
}
|
||||
old := Comparable(seed, pa)
|
||||
stackGrow(8192)
|
||||
new := Comparable(seed, pa)
|
||||
if old != new {
|
||||
t.Fatal("Comparable(seed, ptr) != Comparable(seed, ptr)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var use byte
|
||||
|
||||
//go:noinline
|
||||
func stackGrow(dep int) {
|
||||
if dep == 0 {
|
||||
return
|
||||
}
|
||||
var local [1024]byte
|
||||
// make sure local is allocated on the stack.
|
||||
local[rand.Uint64()%1024] = byte(rand.Uint64())
|
||||
use = local[rand.Uint64()%1024]
|
||||
stackGrow(dep - 1)
|
||||
}
|
||||
|
||||
func TestWriteComparable(t *testing.T) {
|
||||
testWriteComparable(t, int64(2))
|
||||
testWriteComparable(t, uint64(8))
|
||||
testWriteComparable(t, uintptr(12))
|
||||
testWriteComparable(t, any("s"))
|
||||
testWriteComparable(t, "s")
|
||||
testComparable(t, true)
|
||||
testWriteComparable(t, new(float64))
|
||||
testWriteComparable(t, float64(9))
|
||||
testWriteComparable(t, complex128(9i+1))
|
||||
testWriteComparable(t, struct{}{})
|
||||
testWriteComparable(t, struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
type S struct {
|
||||
s string
|
||||
}
|
||||
s1 := S{s: heapStr(t)}
|
||||
s2 := S{s: heapStr(t)}
|
||||
if unsafe.StringData(s1.s) == unsafe.StringData(s2.s) {
|
||||
t.Fatalf("unexpected two heapStr ptr equal")
|
||||
}
|
||||
if s1.s != s2.s {
|
||||
t.Fatalf("unexpected two heapStr value not equal")
|
||||
}
|
||||
testWriteComparable(t, s1, s2)
|
||||
testWriteComparable(t, s1.s, s2.s)
|
||||
testWriteComparable(t, float32(0), negativeZero[float32]())
|
||||
testWriteComparable(t, float64(0), negativeZero[float64]())
|
||||
testWriteComparableNoEqual(t, math.NaN(), math.NaN())
|
||||
testWriteComparableNoEqual(t, [2]string{"a", ""}, [2]string{"", "a"})
|
||||
testWriteComparableNoEqual(t, struct{ a, b string }{"foo", ""}, struct{ a, b string }{"", "foo"})
|
||||
testWriteComparableNoEqual(t, struct{ a, b any }{int(0), struct{}{}}, struct{ a, b any }{struct{}{}, int(0)})
|
||||
}
|
||||
|
||||
func testWriteComparableNoEqual[T comparable](t *testing.T, v1, v2 T) {
|
||||
seed := MakeSeed()
|
||||
h1 := Hash{}
|
||||
h2 := Hash{}
|
||||
*(*Seed)(unsafe.Pointer(&h1)), *(*Seed)(unsafe.Pointer(&h2)) = seed, seed
|
||||
WriteComparable(&h1, v1)
|
||||
WriteComparable(&h2, v2)
|
||||
if h1.Sum64() == h2.Sum64() {
|
||||
t.Fatalf("WriteComparable(seed, %v) == WriteComparable(seed, %v)", v1, v2)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testWriteComparable[T comparable](t *testing.T, v T, v2 ...T) {
|
||||
t.Run(TypeFor[T]().String(), func(t *testing.T) {
|
||||
var a, b T = v, v
|
||||
if len(v2) != 0 {
|
||||
b = v2[0]
|
||||
}
|
||||
var pa *T = &a
|
||||
h1 := Hash{}
|
||||
h2 := Hash{}
|
||||
*(*Seed)(unsafe.Pointer(&h1)) = MakeSeed()
|
||||
h2 = h1
|
||||
WriteComparable(&h1, a)
|
||||
WriteComparable(&h2, b)
|
||||
if h1.Sum64() != h2.Sum64() {
|
||||
t.Fatalf("WriteComparable(h, %v) != WriteComparable(h, %v)", a, b)
|
||||
}
|
||||
WriteComparable(&h1, pa)
|
||||
old := h1.Sum64()
|
||||
stackGrow(8192)
|
||||
WriteComparable(&h2, pa)
|
||||
new := h2.Sum64()
|
||||
if old != new {
|
||||
t.Fatal("WriteComparable(seed, ptr) != WriteComparable(seed, ptr)")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestComparableShouldPanic(t *testing.T) {
|
||||
s := []byte("s")
|
||||
a := any(s)
|
||||
defer func() {
|
||||
e := recover()
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
t.Fatalf("Comaparable(any([]byte)) should panic")
|
||||
}
|
||||
want := "hash of unhashable type []uint8"
|
||||
if s := err.Error(); !strings.Contains(s, want) {
|
||||
t.Fatalf("want %s, got %s", want, s)
|
||||
}
|
||||
}()
|
||||
Comparable(MakeSeed(), a)
|
||||
}
|
||||
|
||||
func TestWriteComparableNoncommute(t *testing.T) {
|
||||
seed := MakeSeed()
|
||||
var h1, h2 Hash
|
||||
h1.SetSeed(seed)
|
||||
h2.SetSeed(seed)
|
||||
|
||||
h1.WriteString("abc")
|
||||
WriteComparable(&h1, 123)
|
||||
WriteComparable(&h2, 123)
|
||||
h2.WriteString("abc")
|
||||
|
||||
if h1.Sum64() == h2.Sum64() {
|
||||
t.Errorf("WriteComparable and WriteString unexpectedly commute")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComparableAllocations(t *testing.T) {
|
||||
t.Skip("test broken in old golang version")
|
||||
seed := MakeSeed()
|
||||
x := heapStr(t)
|
||||
allocs := testing.AllocsPerRun(10, func() {
|
||||
s := "s" + x
|
||||
Comparable(seed, s)
|
||||
})
|
||||
if allocs > 0 {
|
||||
t.Errorf("got %v allocs, want 0", allocs)
|
||||
}
|
||||
|
||||
type S struct {
|
||||
a int
|
||||
b string
|
||||
}
|
||||
allocs = testing.AllocsPerRun(10, func() {
|
||||
s := S{123, "s" + x}
|
||||
Comparable(seed, s)
|
||||
})
|
||||
if allocs > 0 {
|
||||
t.Errorf("got %v allocs, want 0", allocs)
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a Hash implements the hash.Hash and hash.Hash64 interfaces.
|
||||
var _ hash.Hash = &Hash{}
|
||||
var _ hash.Hash64 = &Hash{}
|
||||
|
||||
func benchmarkSize(b *testing.B, size int) {
|
||||
h := &Hash{}
|
||||
buf := make([]byte, size)
|
||||
s := string(buf)
|
||||
|
||||
b.Run("Write", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
for i := 0; i < b.N; i++ {
|
||||
h.Reset()
|
||||
h.Write(buf)
|
||||
h.Sum64()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("Bytes", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
seed := h.Seed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Bytes(seed, buf)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("String", func(b *testing.B) {
|
||||
b.SetBytes(int64(size))
|
||||
seed := h.Seed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
String(seed, s)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkHash(b *testing.B) {
|
||||
sizes := []int{4, 8, 16, 32, 64, 256, 320, 1024, 4096, 16384}
|
||||
for _, size := range sizes {
|
||||
b.Run(fmt.Sprint("n=", size), func(b *testing.B) {
|
||||
benchmarkSize(b, size)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func benchmarkComparable[T comparable](b *testing.B, v T) {
|
||||
b.Run(TypeFor[T]().String(), func(b *testing.B) {
|
||||
seed := MakeSeed()
|
||||
for i := 0; i < b.N; i++ {
|
||||
Comparable(seed, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkComparable(b *testing.B) {
|
||||
type testStruct struct {
|
||||
i int
|
||||
u uint
|
||||
b bool
|
||||
f float64
|
||||
p *int
|
||||
a any
|
||||
}
|
||||
benchmarkComparable(b, int64(2))
|
||||
benchmarkComparable(b, uint64(8))
|
||||
benchmarkComparable(b, uintptr(12))
|
||||
benchmarkComparable(b, any("s"))
|
||||
benchmarkComparable(b, "s")
|
||||
benchmarkComparable(b, true)
|
||||
benchmarkComparable(b, new(float64))
|
||||
benchmarkComparable(b, float64(9))
|
||||
benchmarkComparable(b, complex128(9i+1))
|
||||
benchmarkComparable(b, struct{}{})
|
||||
benchmarkComparable(b, testStruct{i: 9, u: 1, b: true, f: 9.9, p: new(int), a: 1})
|
||||
}
|
||||
|
||||
// TypeFor returns the [Type] that represents the type argument T.
|
||||
func TypeFor[T any]() reflect.Type {
|
||||
var v T
|
||||
if t := reflect.TypeOf(v); t != nil {
|
||||
return t // optimize for T being a non-interface kind
|
||||
}
|
||||
return reflect.TypeOf((*T)(nil)).Elem() // only for an interface kind
|
||||
}
|
||||
@@ -149,6 +149,10 @@ func (c *Conn) ReaderReplaceable() bool {
|
||||
return c.disablePipe.Load() || c.deadline.Load().IsZero()
|
||||
}
|
||||
|
||||
func (c *Conn) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *Conn) Upstream() any {
|
||||
return c.ExtendedConn
|
||||
}
|
||||
|
||||
@@ -22,6 +22,10 @@ type ExtendedReader = network.ExtendedReader
|
||||
|
||||
var WriteBuffer = bufio.WriteBuffer
|
||||
|
||||
type ReadWaitOptions = network.ReadWaitOptions
|
||||
|
||||
var NewReadWaitOptions = network.NewReadWaitOptions
|
||||
|
||||
func NewDeadlineConn(conn net.Conn) ExtendedConn {
|
||||
if deadline.IsPipe(conn) || deadline.IsPipe(network.UnwrapReader(conn)) {
|
||||
return NewExtendedConn(conn) // pipe always have correctly deadline implement
|
||||
|
||||
@@ -8,18 +8,23 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var defaultAllocator = NewAllocator()
|
||||
var DefaultAllocator = NewAllocator()
|
||||
|
||||
// Allocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type Allocator struct {
|
||||
type Allocator interface {
|
||||
Get(size int) []byte
|
||||
Put(buf []byte) error
|
||||
}
|
||||
|
||||
// defaultAllocator for incoming frames, optimized to prevent overwriting after zeroing
|
||||
type defaultAllocator struct {
|
||||
buffers [11]sync.Pool
|
||||
}
|
||||
|
||||
// NewAllocator initiates a []byte allocator for frames less than 65536 bytes,
|
||||
// the waste(memory fragmentation) of space allocation is guaranteed to be
|
||||
// no more than 50%.
|
||||
func NewAllocator() *Allocator {
|
||||
return &Allocator{
|
||||
func NewAllocator() Allocator {
|
||||
return &defaultAllocator{
|
||||
buffers: [...]sync.Pool{ // 64B -> 64K
|
||||
{New: func() any { return new([1 << 6]byte) }},
|
||||
{New: func() any { return new([1 << 7]byte) }},
|
||||
@@ -37,7 +42,7 @@ func NewAllocator() *Allocator {
|
||||
}
|
||||
|
||||
// Get a []byte from pool with most appropriate cap
|
||||
func (alloc *Allocator) Get(size int) []byte {
|
||||
func (alloc *defaultAllocator) Get(size int) []byte {
|
||||
switch {
|
||||
case size < 0:
|
||||
panic("alloc.Get: len out of range")
|
||||
@@ -87,7 +92,7 @@ func (alloc *Allocator) Get(size int) []byte {
|
||||
|
||||
// Put returns a []byte to pool for future use,
|
||||
// which the cap must be exactly 2^n
|
||||
func (alloc *Allocator) Put(buf []byte) error {
|
||||
func (alloc *defaultAllocator) Put(buf []byte) error {
|
||||
if cap(buf) == 0 || cap(buf) > 65536 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
package pool
|
||||
|
||||
const (
|
||||
// RelayBufferSize using for tcp
|
||||
// io.Copy default buffer size is 32 KiB
|
||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||
RelayBufferSize = 16 * 1024
|
||||
|
||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// UDPBufferSize using for udp
|
||||
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||
UDPBufferSize = 8 * 1024
|
||||
)
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
package pool
|
||||
|
||||
const (
|
||||
// RelayBufferSize using for tcp
|
||||
// io.Copy default buffer size is 32 KiB
|
||||
// but the maximum packet size of vmess/shadowsocks is about 16 KiB
|
||||
// so define a buffer of 20 KiB to reduce the memory of each TCP relay
|
||||
RelayBufferSize = 20 * 1024
|
||||
RelayBufferSize = 32 * 1024
|
||||
|
||||
// RelayBufferSize uses 20KiB, but due to the allocator it will actually
|
||||
// request 32Kib. Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// UDPBufferSize using for udp
|
||||
// Most UDPs are smaller than the MTU, and the TUN's MTU
|
||||
// set to 9000, so the UDP Buffer size set to 16Kib
|
||||
UDPBufferSize = 16 * 1024
|
||||
)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package pool
|
||||
|
||||
func Get(size int) []byte {
|
||||
return defaultAllocator.Get(size)
|
||||
return DefaultAllocator.Get(size)
|
||||
}
|
||||
|
||||
func Put(buf []byte) error {
|
||||
return defaultAllocator.Put(buf)
|
||||
return DefaultAllocator.Put(buf)
|
||||
}
|
||||
|
||||
@@ -3,5 +3,5 @@ package pool
|
||||
import "github.com/metacubex/sing/common/buf"
|
||||
|
||||
func init() {
|
||||
buf.DefaultAllocator = defaultAllocator
|
||||
buf.DefaultAllocator = DefaultAllocator
|
||||
}
|
||||
|
||||
@@ -239,13 +239,15 @@ func (n *num) UnmarshalText(text []byte) (err error) {
|
||||
|
||||
func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"num": "255",
|
||||
"num_p": "127",
|
||||
"num": "255",
|
||||
"num_p": "127",
|
||||
"num_arr": []string{"1", "2", "3"},
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Num num `test:"num"`
|
||||
NumP *num `test:"num_p"`
|
||||
Num num `test:"num"`
|
||||
NumP *num `test:"num_p"`
|
||||
NumArr []num `test:"num_arr"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
@@ -253,6 +255,7 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
assert.Equal(t, 255, s.Num.a)
|
||||
assert.NotNil(t, s.NumP)
|
||||
assert.Equal(t, s.NumP.a, 127)
|
||||
assert.Equal(t, s.NumArr, []num{{1}, {2}, {3}})
|
||||
|
||||
// test WeaklyTypedInput
|
||||
rawMap["num"] = 256
|
||||
|
||||
926
common/xsync/map.go
Normal file
926
common/xsync/map.go
Normal file
@@ -0,0 +1,926 @@
|
||||
package xsync
|
||||
|
||||
// copy and modified from https://github.com/puzpuzpuz/xsync/blob/v4.1.0/map.go
|
||||
// which is licensed under Apache v2.
|
||||
//
|
||||
// mihomo modified:
|
||||
// 1. parallel Map resize has been removed to decrease the memory using.
|
||||
// 2. the zero Map is ready for use.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/maphash"
|
||||
)
|
||||
|
||||
const (
|
||||
// number of Map entries per bucket; 5 entries lead to size of 64B
|
||||
// (one cache line) on 64-bit machines
|
||||
entriesPerMapBucket = 5
|
||||
// threshold fraction of table occupation to start a table shrinking
|
||||
// when deleting the last entry in a bucket chain
|
||||
mapShrinkFraction = 128
|
||||
// map load factor to trigger a table resize during insertion;
|
||||
// a map holds up to mapLoadFactor*entriesPerMapBucket*mapTableLen
|
||||
// key-value pairs (this is a soft limit)
|
||||
mapLoadFactor = 0.75
|
||||
// minimal table size, i.e. number of buckets; thus, minimal map
|
||||
// capacity can be calculated as entriesPerMapBucket*defaultMinMapTableLen
|
||||
defaultMinMapTableLen = 32
|
||||
// minimum counter stripes to use
|
||||
minMapCounterLen = 8
|
||||
// maximum counter stripes to use; stands for around 4KB of memory
|
||||
maxMapCounterLen = 32
|
||||
defaultMeta uint64 = 0x8080808080808080
|
||||
metaMask uint64 = 0xffffffffff
|
||||
defaultMetaMasked uint64 = defaultMeta & metaMask
|
||||
emptyMetaSlot uint8 = 0x80
|
||||
)
|
||||
|
||||
type mapResizeHint int
|
||||
|
||||
const (
|
||||
mapGrowHint mapResizeHint = 0
|
||||
mapShrinkHint mapResizeHint = 1
|
||||
mapClearHint mapResizeHint = 2
|
||||
)
|
||||
|
||||
type ComputeOp int
|
||||
|
||||
const (
|
||||
// CancelOp signals to Compute to not do anything as a result
|
||||
// of executing the lambda. If the entry was not present in
|
||||
// the map, nothing happens, and if it was present, the
|
||||
// returned value is ignored.
|
||||
CancelOp ComputeOp = iota
|
||||
// UpdateOp signals to Compute to update the entry to the
|
||||
// value returned by the lambda, creating it if necessary.
|
||||
UpdateOp
|
||||
// DeleteOp signals to Compute to always delete the entry
|
||||
// from the map.
|
||||
DeleteOp
|
||||
)
|
||||
|
||||
type loadOp int
|
||||
|
||||
const (
|
||||
noLoadOp loadOp = iota
|
||||
loadOrComputeOp
|
||||
loadAndDeleteOp
|
||||
)
|
||||
|
||||
// Map is like a Go map[K]V but is safe for concurrent
|
||||
// use by multiple goroutines without additional locking or
|
||||
// coordination. It follows the interface of sync.Map with
|
||||
// a number of valuable extensions like Compute or Size.
|
||||
//
|
||||
// A Map must not be copied after first use.
|
||||
//
|
||||
// Map uses a modified version of Cache-Line Hash Table (CLHT)
|
||||
// data structure: https://github.com/LPD-EPFL/CLHT
|
||||
//
|
||||
// CLHT is built around idea to organize the hash table in
|
||||
// cache-line-sized buckets, so that on all modern CPUs update
|
||||
// operations complete with at most one cache-line transfer.
|
||||
// Also, Get operations involve no write to memory, as well as no
|
||||
// mutexes or any other sort of locks. Due to this design, in all
|
||||
// considered scenarios Map outperforms sync.Map.
|
||||
//
|
||||
// Map also borrows ideas from Java's j.u.c.ConcurrentHashMap
|
||||
// (immutable K/V pair structs instead of atomic snapshots)
|
||||
// and C++'s absl::flat_hash_map (meta memory and SWAR-based
|
||||
// lookups).
|
||||
type Map[K comparable, V any] struct {
|
||||
initOnce sync.Once
|
||||
totalGrowths atomic.Int64
|
||||
totalShrinks atomic.Int64
|
||||
resizing atomic.Bool // resize in progress flag
|
||||
resizeMu sync.Mutex // only used along with resizeCond
|
||||
resizeCond sync.Cond // used to wake up resize waiters (concurrent modifications)
|
||||
table atomic.Pointer[mapTable[K, V]]
|
||||
minTableLen int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
type mapTable[K comparable, V any] struct {
|
||||
buckets []bucketPadded[K, V]
|
||||
// striped counter for number of table entries;
|
||||
// used to determine if a table shrinking is needed
|
||||
// occupies min(buckets_memory/1024, 64KB) of memory
|
||||
size []counterStripe
|
||||
seed maphash.Seed
|
||||
}
|
||||
|
||||
type counterStripe struct {
|
||||
c int64
|
||||
// Padding to prevent false sharing.
|
||||
_ [cacheLineSize - 8]byte
|
||||
}
|
||||
|
||||
// bucketPadded is a CL-sized map bucket holding up to
|
||||
// entriesPerMapBucket entries.
|
||||
type bucketPadded[K comparable, V any] struct {
|
||||
//lint:ignore U1000 ensure each bucket takes two cache lines on both 32 and 64-bit archs
|
||||
pad [cacheLineSize - unsafe.Sizeof(bucket[K, V]{})]byte
|
||||
bucket[K, V]
|
||||
}
|
||||
|
||||
type bucket[K comparable, V any] struct {
|
||||
meta atomic.Uint64
|
||||
entries [entriesPerMapBucket]atomic.Pointer[entry[K, V]] // *entry
|
||||
next atomic.Pointer[bucketPadded[K, V]] // *bucketPadded
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// entry is an immutable map entry.
|
||||
type entry[K comparable, V any] struct {
|
||||
key K
|
||||
value V
|
||||
}
|
||||
|
||||
// MapConfig defines configurable Map options.
|
||||
type MapConfig struct {
|
||||
sizeHint int
|
||||
growOnly bool
|
||||
}
|
||||
|
||||
// WithPresize configures new Map instance with capacity enough
|
||||
// to hold sizeHint entries. The capacity is treated as the minimal
|
||||
// capacity meaning that the underlying hash table will never shrink
|
||||
// to a smaller capacity. If sizeHint is zero or negative, the value
|
||||
// is ignored.
|
||||
func WithPresize(sizeHint int) func(*MapConfig) {
|
||||
return func(c *MapConfig) {
|
||||
c.sizeHint = sizeHint
|
||||
}
|
||||
}
|
||||
|
||||
// WithGrowOnly configures new Map instance to be grow-only.
|
||||
// This means that the underlying hash table grows in capacity when
|
||||
// new keys are added, but does not shrink when keys are deleted.
|
||||
// The only exception to this rule is the Clear method which
|
||||
// shrinks the hash table back to the initial capacity.
|
||||
func WithGrowOnly() func(*MapConfig) {
|
||||
return func(c *MapConfig) {
|
||||
c.growOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
// NewMap creates a new Map instance configured with the given
|
||||
// options.
|
||||
func NewMap[K comparable, V any](options ...func(*MapConfig)) *Map[K, V] {
|
||||
c := &MapConfig{}
|
||||
for _, o := range options {
|
||||
o(c)
|
||||
}
|
||||
|
||||
m := &Map[K, V]{}
|
||||
if c.sizeHint > defaultMinMapTableLen*entriesPerMapBucket {
|
||||
tableLen := nextPowOf2(uint32((float64(c.sizeHint) / entriesPerMapBucket) / mapLoadFactor))
|
||||
m.minTableLen = int(tableLen)
|
||||
}
|
||||
m.growOnly = c.growOnly
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) init() {
|
||||
if m.minTableLen == 0 {
|
||||
m.minTableLen = defaultMinMapTableLen
|
||||
}
|
||||
m.resizeCond = *sync.NewCond(&m.resizeMu)
|
||||
table := newMapTable[K, V](m.minTableLen)
|
||||
m.minTableLen = len(table.buckets)
|
||||
m.table.Store(table)
|
||||
}
|
||||
|
||||
func newMapTable[K comparable, V any](minTableLen int) *mapTable[K, V] {
|
||||
buckets := make([]bucketPadded[K, V], minTableLen)
|
||||
for i := range buckets {
|
||||
buckets[i].meta.Store(defaultMeta)
|
||||
}
|
||||
counterLen := minTableLen >> 10
|
||||
if counterLen < minMapCounterLen {
|
||||
counterLen = minMapCounterLen
|
||||
} else if counterLen > maxMapCounterLen {
|
||||
counterLen = maxMapCounterLen
|
||||
}
|
||||
counter := make([]counterStripe, counterLen)
|
||||
t := &mapTable[K, V]{
|
||||
buckets: buckets,
|
||||
size: counter,
|
||||
seed: maphash.MakeSeed(),
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// ToPlainMap returns a native map with a copy of xsync Map's
|
||||
// contents. The copied xsync Map should not be modified while
|
||||
// this call is made. If the copied Map is modified, the copying
|
||||
// behavior is the same as in the Range method.
|
||||
func ToPlainMap[K comparable, V any](m *Map[K, V]) map[K]V {
|
||||
pm := make(map[K]V)
|
||||
if m != nil {
|
||||
m.Range(func(key K, value V) bool {
|
||||
pm[key] = value
|
||||
return true
|
||||
})
|
||||
}
|
||||
return pm
|
||||
}
|
||||
|
||||
// Load returns the value stored in the map for a key, or zero value
|
||||
// of type V if no value is present.
|
||||
// The ok result indicates whether value was found in the map.
|
||||
func (m *Map[K, V]) Load(key K) (value V, ok bool) {
|
||||
m.initOnce.Do(m.init)
|
||||
table := m.table.Load()
|
||||
hash := maphash.Comparable(table.seed, key)
|
||||
h1 := h1(hash)
|
||||
h2w := broadcast(h2(hash))
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
b := &table.buckets[bidx]
|
||||
for {
|
||||
metaw := b.meta.Load()
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
e := b.entries[idx].Load()
|
||||
if e != nil {
|
||||
if e.key == key {
|
||||
return e.value, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
b = b.next.Load()
|
||||
if b == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store sets the value for a key.
|
||||
func (m *Map[K, V]) Store(key K, value V) {
|
||||
m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, ComputeOp) {
|
||||
return value, UpdateOp
|
||||
},
|
||||
noLoadOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
// Otherwise, it stores and returns the given value.
|
||||
// The loaded result is true if the value was loaded, false if stored.
|
||||
func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(oldValue V, loaded bool) (V, ComputeOp) {
|
||||
if loaded {
|
||||
return oldValue, CancelOp
|
||||
}
|
||||
return value, UpdateOp
|
||||
},
|
||||
loadOrComputeOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadAndStore returns the existing value for the key if present,
|
||||
// while setting the new value for the key.
|
||||
// It stores the new value and returns the existing one, if present.
|
||||
// The loaded result is true if the existing value was loaded,
|
||||
// false otherwise.
|
||||
func (m *Map[K, V]) LoadAndStore(key K, value V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(V, bool) (V, ComputeOp) {
|
||||
return value, UpdateOp
|
||||
},
|
||||
noLoadOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// LoadOrCompute returns the existing value for the key if
|
||||
// present. Otherwise, it tries to compute the value using the
|
||||
// provided function and, if successful, stores and returns
|
||||
// the computed value. The loaded result is true if the value was
|
||||
// loaded, or false if computed. If valueFn returns true as the
|
||||
// cancel value, the computation is cancelled and the zero value
|
||||
// for type V is returned.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *Map[K, V]) LoadOrCompute(
|
||||
key K,
|
||||
valueFn func() (newValue V, cancel bool),
|
||||
) (value V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(oldValue V, loaded bool) (V, ComputeOp) {
|
||||
if loaded {
|
||||
return oldValue, CancelOp
|
||||
}
|
||||
newValue, c := valueFn()
|
||||
if !c {
|
||||
return newValue, UpdateOp
|
||||
}
|
||||
return oldValue, CancelOp
|
||||
},
|
||||
loadOrComputeOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Compute either sets the computed new value for the key,
|
||||
// deletes the value for the key, or does nothing, based on
|
||||
// the returned [ComputeOp]. When the op returned by valueFn
|
||||
// is [UpdateOp], the value is updated to the new value. If
|
||||
// it is [DeleteOp], the entry is removed from the map
|
||||
// altogether. And finally, if the op is [CancelOp] then the
|
||||
// entry is left as-is. In other words, if it did not already
|
||||
// exist, it is not created, and if it did exist, it is not
|
||||
// updated. This is useful to synchronously execute some
|
||||
// operation on the value without incurring the cost of
|
||||
// updating the map every time. The ok result indicates
|
||||
// whether the entry is present in the map after the compute
|
||||
// operation. The actual result contains the value of the map
|
||||
// if a corresponding entry is present, or the zero value
|
||||
// otherwise. See the example for a few use cases.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
func (m *Map[K, V]) Compute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (newValue V, op ComputeOp),
|
||||
) (actual V, ok bool) {
|
||||
return m.doCompute(key, valueFn, noLoadOp, true)
|
||||
}
|
||||
|
||||
// LoadAndDelete deletes the value for a key, returning the previous
|
||||
// value if any. The loaded result reports whether the key was
|
||||
// present.
|
||||
func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(value V, loaded bool) (V, ComputeOp) {
|
||||
return value, DeleteOp
|
||||
},
|
||||
loadAndDeleteOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (m *Map[K, V]) Delete(key K) {
|
||||
m.LoadAndDelete(key)
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) doCompute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (V, ComputeOp),
|
||||
loadOp loadOp,
|
||||
computeOnly bool,
|
||||
) (V, bool) {
|
||||
m.initOnce.Do(m.init)
|
||||
for {
|
||||
compute_attempt:
|
||||
var (
|
||||
emptyb *bucketPadded[K, V]
|
||||
emptyidx int
|
||||
)
|
||||
table := m.table.Load()
|
||||
tableLen := len(table.buckets)
|
||||
hash := maphash.Comparable(table.seed, key)
|
||||
h1 := h1(hash)
|
||||
h2 := h2(hash)
|
||||
h2w := broadcast(h2)
|
||||
bidx := uint64(len(table.buckets)-1) & h1
|
||||
rootb := &table.buckets[bidx]
|
||||
|
||||
if loadOp != noLoadOp {
|
||||
b := rootb
|
||||
load:
|
||||
for {
|
||||
metaw := b.meta.Load()
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
e := b.entries[idx].Load()
|
||||
if e != nil {
|
||||
if e.key == key {
|
||||
if loadOp == loadOrComputeOp {
|
||||
return e.value, true
|
||||
}
|
||||
break load
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
b = b.next.Load()
|
||||
if b == nil {
|
||||
if loadOp == loadAndDeleteOp {
|
||||
return *new(V), false
|
||||
}
|
||||
break load
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootb.mu.Lock()
|
||||
// The following two checks must go in reverse to what's
|
||||
// in the resize method.
|
||||
if m.resizeInProgress() {
|
||||
// Resize is in progress. Wait, then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.waitForResize()
|
||||
goto compute_attempt
|
||||
}
|
||||
if m.newerTableExists(table) {
|
||||
// Someone resized the table. Go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
goto compute_attempt
|
||||
}
|
||||
b := rootb
|
||||
for {
|
||||
metaw := b.meta.Load()
|
||||
markedw := markZeroBytes(metaw^h2w) & metaMask
|
||||
for markedw != 0 {
|
||||
idx := firstMarkedByteIndex(markedw)
|
||||
e := b.entries[idx].Load()
|
||||
if e != nil {
|
||||
if e.key == key {
|
||||
// In-place update/delete.
|
||||
// We get a copy of the value via an interface{} on each call,
|
||||
// thus the live value pointers are unique. Otherwise atomic
|
||||
// snapshot won't be correct in case of multiple Store calls
|
||||
// using the same value.
|
||||
oldv := e.value
|
||||
newv, op := valueFn(oldv, true)
|
||||
switch op {
|
||||
case DeleteOp:
|
||||
// Deletion.
|
||||
// First we update the hash, then the entry.
|
||||
newmetaw := setByte(metaw, emptyMetaSlot, idx)
|
||||
b.meta.Store(newmetaw)
|
||||
b.entries[idx].Store(nil)
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, -1)
|
||||
// Might need to shrink the table if we left bucket empty.
|
||||
if newmetaw == defaultMeta {
|
||||
m.resize(table, mapShrinkHint)
|
||||
}
|
||||
return oldv, !computeOnly
|
||||
case UpdateOp:
|
||||
newe := new(entry[K, V])
|
||||
newe.key = key
|
||||
newe.value = newv
|
||||
b.entries[idx].Store(newe)
|
||||
case CancelOp:
|
||||
newv = oldv
|
||||
}
|
||||
rootb.mu.Unlock()
|
||||
if computeOnly {
|
||||
// Compute expects the new value to be returned.
|
||||
return newv, true
|
||||
}
|
||||
// LoadAndStore expects the old value to be returned.
|
||||
return oldv, true
|
||||
}
|
||||
}
|
||||
markedw &= markedw - 1
|
||||
}
|
||||
if emptyb == nil {
|
||||
// Search for empty entries (up to 5 per bucket).
|
||||
emptyw := metaw & defaultMetaMasked
|
||||
if emptyw != 0 {
|
||||
idx := firstMarkedByteIndex(emptyw)
|
||||
emptyb = b
|
||||
emptyidx = idx
|
||||
}
|
||||
}
|
||||
if b.next.Load() == nil {
|
||||
if emptyb != nil {
|
||||
// Insertion into an existing bucket.
|
||||
var zeroV V
|
||||
newValue, op := valueFn(zeroV, false)
|
||||
switch op {
|
||||
case DeleteOp, CancelOp:
|
||||
rootb.mu.Unlock()
|
||||
return zeroV, false
|
||||
default:
|
||||
newe := new(entry[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
// First we update meta, then the entry.
|
||||
emptyb.meta.Store(setByte(emptyb.meta.Load(), h2, emptyidx))
|
||||
emptyb.entries[emptyidx].Store(newe)
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
}
|
||||
growThreshold := float64(tableLen) * entriesPerMapBucket * mapLoadFactor
|
||||
if table.sumSize() > int64(growThreshold) {
|
||||
// Need to grow the table. Then go for another attempt.
|
||||
rootb.mu.Unlock()
|
||||
m.resize(table, mapGrowHint)
|
||||
goto compute_attempt
|
||||
}
|
||||
// Insertion into a new bucket.
|
||||
var zeroV V
|
||||
newValue, op := valueFn(zeroV, false)
|
||||
switch op {
|
||||
case DeleteOp, CancelOp:
|
||||
rootb.mu.Unlock()
|
||||
return newValue, false
|
||||
default:
|
||||
// Create and append a bucket.
|
||||
newb := new(bucketPadded[K, V])
|
||||
newb.meta.Store(setByte(defaultMeta, h2, 0))
|
||||
newe := new(entry[K, V])
|
||||
newe.key = key
|
||||
newe.value = newValue
|
||||
newb.entries[0].Store(newe)
|
||||
b.next.Store(newb)
|
||||
rootb.mu.Unlock()
|
||||
table.addSize(bidx, 1)
|
||||
return newValue, computeOnly
|
||||
}
|
||||
}
|
||||
b = b.next.Load()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) newerTableExists(table *mapTable[K, V]) bool {
|
||||
return table != m.table.Load()
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) resizeInProgress() bool {
|
||||
return m.resizing.Load()
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) waitForResize() {
|
||||
m.resizeMu.Lock()
|
||||
for m.resizeInProgress() {
|
||||
m.resizeCond.Wait()
|
||||
}
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func (m *Map[K, V]) resize(knownTable *mapTable[K, V], hint mapResizeHint) {
|
||||
knownTableLen := len(knownTable.buckets)
|
||||
// Fast path for shrink attempts.
|
||||
if hint == mapShrinkHint {
|
||||
if m.growOnly ||
|
||||
m.minTableLen == knownTableLen ||
|
||||
knownTable.sumSize() > int64((knownTableLen*entriesPerMapBucket)/mapShrinkFraction) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Slow path.
|
||||
if !m.resizing.CompareAndSwap(false, true) {
|
||||
// Someone else started resize. Wait for it to finish.
|
||||
m.waitForResize()
|
||||
return
|
||||
}
|
||||
var newTable *mapTable[K, V]
|
||||
table := m.table.Load()
|
||||
tableLen := len(table.buckets)
|
||||
switch hint {
|
||||
case mapGrowHint:
|
||||
// Grow the table with factor of 2.
|
||||
m.totalGrowths.Add(1)
|
||||
newTable = newMapTable[K, V](tableLen << 1)
|
||||
case mapShrinkHint:
|
||||
shrinkThreshold := int64((tableLen * entriesPerMapBucket) / mapShrinkFraction)
|
||||
if tableLen > m.minTableLen && table.sumSize() <= shrinkThreshold {
|
||||
// Shrink the table with factor of 2.
|
||||
m.totalShrinks.Add(1)
|
||||
newTable = newMapTable[K, V](tableLen >> 1)
|
||||
} else {
|
||||
// No need to shrink. Wake up all waiters and give up.
|
||||
m.resizeMu.Lock()
|
||||
m.resizing.Store(false)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
return
|
||||
}
|
||||
case mapClearHint:
|
||||
newTable = newMapTable[K, V](m.minTableLen)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected resize hint: %d", hint))
|
||||
}
|
||||
// Copy the data only if we're not clearing the map.
|
||||
if hint != mapClearHint {
|
||||
for i := 0; i < tableLen; i++ {
|
||||
copied := copyBucket(&table.buckets[i], newTable)
|
||||
newTable.addSizePlain(uint64(i), copied)
|
||||
}
|
||||
}
|
||||
// Publish the new table and wake up all waiters.
|
||||
m.table.Store(newTable)
|
||||
m.resizeMu.Lock()
|
||||
m.resizing.Store(false)
|
||||
m.resizeCond.Broadcast()
|
||||
m.resizeMu.Unlock()
|
||||
}
|
||||
|
||||
func copyBucket[K comparable, V any](
|
||||
b *bucketPadded[K, V],
|
||||
destTable *mapTable[K, V],
|
||||
) (copied int) {
|
||||
rootb := b
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if e := b.entries[i].Load(); e != nil {
|
||||
hash := maphash.Comparable(destTable.seed, e.key)
|
||||
bidx := uint64(len(destTable.buckets)-1) & h1(hash)
|
||||
destb := &destTable.buckets[bidx]
|
||||
appendToBucket(h2(hash), b.entries[i].Load(), destb)
|
||||
copied++
|
||||
}
|
||||
}
|
||||
if next := b.next.Load(); next == nil {
|
||||
rootb.mu.Unlock()
|
||||
return
|
||||
} else {
|
||||
b = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Range calls f sequentially for each key and value present in the
|
||||
// map. If f returns false, range stops the iteration.
|
||||
//
|
||||
// Range does not necessarily correspond to any consistent snapshot
|
||||
// of the Map's contents: no key will be visited more than once, but
|
||||
// if the value for any key is stored or deleted concurrently, Range
|
||||
// may reflect any mapping for that key from any point during the
|
||||
// Range call.
|
||||
//
|
||||
// It is safe to modify the map while iterating it, including entry
|
||||
// creation, modification and deletion. However, the concurrent
|
||||
// modification rule apply, i.e. the changes may be not reflected
|
||||
// in the subsequently iterated entries.
|
||||
func (m *Map[K, V]) Range(f func(key K, value V) bool) {
|
||||
m.initOnce.Do(m.init)
|
||||
// Pre-allocate array big enough to fit entries for most hash tables.
|
||||
bentries := make([]*entry[K, V], 0, 16*entriesPerMapBucket)
|
||||
table := m.table.Load()
|
||||
for i := range table.buckets {
|
||||
rootb := &table.buckets[i]
|
||||
b := rootb
|
||||
// Prevent concurrent modifications and copy all entries into
|
||||
// the intermediate slice.
|
||||
rootb.mu.Lock()
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if entry := b.entries[i].Load(); entry != nil {
|
||||
bentries = append(bentries, entry)
|
||||
}
|
||||
}
|
||||
if next := b.next.Load(); next == nil {
|
||||
rootb.mu.Unlock()
|
||||
break
|
||||
} else {
|
||||
b = next
|
||||
}
|
||||
}
|
||||
// Call the function for all copied entries.
|
||||
for j, e := range bentries {
|
||||
if !f(e.key, e.value) {
|
||||
return
|
||||
}
|
||||
// Remove the reference to avoid preventing the copied
|
||||
// entries from being GCed until this method finishes.
|
||||
bentries[j] = nil
|
||||
}
|
||||
bentries = bentries[:0]
|
||||
}
|
||||
}
|
||||
|
||||
// Clear deletes all keys and values currently stored in the map.
|
||||
func (m *Map[K, V]) Clear() {
|
||||
m.initOnce.Do(m.init)
|
||||
m.resize(m.table.Load(), mapClearHint)
|
||||
}
|
||||
|
||||
// Size returns current size of the map.
|
||||
func (m *Map[K, V]) Size() int {
|
||||
m.initOnce.Do(m.init)
|
||||
return int(m.table.Load().sumSize())
|
||||
}
|
||||
|
||||
func appendToBucket[K comparable, V any](h2 uint8, e *entry[K, V], b *bucketPadded[K, V]) {
|
||||
for {
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.entries[i].Load() == nil {
|
||||
b.meta.Store(setByte(b.meta.Load(), h2, i))
|
||||
b.entries[i].Store(e)
|
||||
return
|
||||
}
|
||||
}
|
||||
if next := b.next.Load(); next == nil {
|
||||
newb := new(bucketPadded[K, V])
|
||||
newb.meta.Store(setByte(defaultMeta, h2, 0))
|
||||
newb.entries[0].Store(e)
|
||||
b.next.Store(newb)
|
||||
return
|
||||
} else {
|
||||
b = next
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (table *mapTable[K, V]) addSize(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
atomic.AddInt64(&table.size[cidx].c, int64(delta))
|
||||
}
|
||||
|
||||
func (table *mapTable[K, V]) addSizePlain(bucketIdx uint64, delta int) {
|
||||
cidx := uint64(len(table.size)-1) & bucketIdx
|
||||
table.size[cidx].c += int64(delta)
|
||||
}
|
||||
|
||||
func (table *mapTable[K, V]) sumSize() int64 {
|
||||
sum := int64(0)
|
||||
for i := range table.size {
|
||||
sum += atomic.LoadInt64(&table.size[i].c)
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func h1(h uint64) uint64 {
|
||||
return h >> 7
|
||||
}
|
||||
|
||||
func h2(h uint64) uint8 {
|
||||
return uint8(h & 0x7f)
|
||||
}
|
||||
|
||||
// MapStats is Map statistics.
|
||||
//
|
||||
// Warning: map statistics are intented to be used for diagnostic
|
||||
// purposes, not for production code. This means that breaking changes
|
||||
// may be introduced into this struct even between minor releases.
|
||||
type MapStats struct {
|
||||
// RootBuckets is the number of root buckets in the hash table.
|
||||
// Each bucket holds a few entries.
|
||||
RootBuckets int
|
||||
// TotalBuckets is the total number of buckets in the hash table,
|
||||
// including root and their chained buckets. Each bucket holds
|
||||
// a few entries.
|
||||
TotalBuckets int
|
||||
// EmptyBuckets is the number of buckets that hold no entries.
|
||||
EmptyBuckets int
|
||||
// Capacity is the Map capacity, i.e. the total number of
|
||||
// entries that all buckets can physically hold. This number
|
||||
// does not consider the load factor.
|
||||
Capacity int
|
||||
// Size is the exact number of entries stored in the map.
|
||||
Size int
|
||||
// Counter is the number of entries stored in the map according
|
||||
// to the internal atomic counter. In case of concurrent map
|
||||
// modifications this number may be different from Size.
|
||||
Counter int
|
||||
// CounterLen is the number of internal atomic counter stripes.
|
||||
// This number may grow with the map capacity to improve
|
||||
// multithreaded scalability.
|
||||
CounterLen int
|
||||
// MinEntries is the minimum number of entries per a chain of
|
||||
// buckets, i.e. a root bucket and its chained buckets.
|
||||
MinEntries int
|
||||
// MinEntries is the maximum number of entries per a chain of
|
||||
// buckets, i.e. a root bucket and its chained buckets.
|
||||
MaxEntries int
|
||||
// TotalGrowths is the number of times the hash table grew.
|
||||
TotalGrowths int64
|
||||
// TotalGrowths is the number of times the hash table shrinked.
|
||||
TotalShrinks int64
|
||||
}
|
||||
|
||||
// ToString returns string representation of map stats.
|
||||
func (s *MapStats) ToString() string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("MapStats{\n")
|
||||
sb.WriteString(fmt.Sprintf("RootBuckets: %d\n", s.RootBuckets))
|
||||
sb.WriteString(fmt.Sprintf("TotalBuckets: %d\n", s.TotalBuckets))
|
||||
sb.WriteString(fmt.Sprintf("EmptyBuckets: %d\n", s.EmptyBuckets))
|
||||
sb.WriteString(fmt.Sprintf("Capacity: %d\n", s.Capacity))
|
||||
sb.WriteString(fmt.Sprintf("Size: %d\n", s.Size))
|
||||
sb.WriteString(fmt.Sprintf("Counter: %d\n", s.Counter))
|
||||
sb.WriteString(fmt.Sprintf("CounterLen: %d\n", s.CounterLen))
|
||||
sb.WriteString(fmt.Sprintf("MinEntries: %d\n", s.MinEntries))
|
||||
sb.WriteString(fmt.Sprintf("MaxEntries: %d\n", s.MaxEntries))
|
||||
sb.WriteString(fmt.Sprintf("TotalGrowths: %d\n", s.TotalGrowths))
|
||||
sb.WriteString(fmt.Sprintf("TotalShrinks: %d\n", s.TotalShrinks))
|
||||
sb.WriteString("}\n")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
// Stats returns statistics for the Map. Just like other map
|
||||
// methods, this one is thread-safe. Yet it's an O(N) operation,
|
||||
// so it should be used only for diagnostics or debugging purposes.
|
||||
func (m *Map[K, V]) Stats() MapStats {
|
||||
m.initOnce.Do(m.init)
|
||||
stats := MapStats{
|
||||
TotalGrowths: m.totalGrowths.Load(),
|
||||
TotalShrinks: m.totalShrinks.Load(),
|
||||
MinEntries: math.MaxInt32,
|
||||
}
|
||||
table := m.table.Load()
|
||||
stats.RootBuckets = len(table.buckets)
|
||||
stats.Counter = int(table.sumSize())
|
||||
stats.CounterLen = len(table.size)
|
||||
for i := range table.buckets {
|
||||
nentries := 0
|
||||
b := &table.buckets[i]
|
||||
stats.TotalBuckets++
|
||||
for {
|
||||
nentriesLocal := 0
|
||||
stats.Capacity += entriesPerMapBucket
|
||||
for i := 0; i < entriesPerMapBucket; i++ {
|
||||
if b.entries[i].Load() != nil {
|
||||
stats.Size++
|
||||
nentriesLocal++
|
||||
}
|
||||
}
|
||||
nentries += nentriesLocal
|
||||
if nentriesLocal == 0 {
|
||||
stats.EmptyBuckets++
|
||||
}
|
||||
if next := b.next.Load(); next == nil {
|
||||
break
|
||||
} else {
|
||||
b = next
|
||||
}
|
||||
stats.TotalBuckets++
|
||||
}
|
||||
if nentries < stats.MinEntries {
|
||||
stats.MinEntries = nentries
|
||||
}
|
||||
if nentries > stats.MaxEntries {
|
||||
stats.MaxEntries = nentries
|
||||
}
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
const (
|
||||
// cacheLineSize is used in paddings to prevent false sharing;
|
||||
// 64B are used instead of 128B as a compromise between
|
||||
// memory footprint and performance; 128B usage may give ~30%
|
||||
// improvement on NUMA machines.
|
||||
cacheLineSize = 64
|
||||
)
|
||||
|
||||
// nextPowOf2 computes the next highest power of 2 of 32-bit v.
|
||||
// Source: https://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||
func nextPowOf2(v uint32) uint32 {
|
||||
if v == 0 {
|
||||
return 1
|
||||
}
|
||||
v--
|
||||
v |= v >> 1
|
||||
v |= v >> 2
|
||||
v |= v >> 4
|
||||
v |= v >> 8
|
||||
v |= v >> 16
|
||||
v++
|
||||
return v
|
||||
}
|
||||
|
||||
func broadcast(b uint8) uint64 {
|
||||
return 0x101010101010101 * uint64(b)
|
||||
}
|
||||
|
||||
func firstMarkedByteIndex(w uint64) int {
|
||||
return bits.TrailingZeros64(w) >> 3
|
||||
}
|
||||
|
||||
// SWAR byte search: may produce false positives, e.g. for 0x0100,
|
||||
// so make sure to double-check bytes found by this function.
|
||||
func markZeroBytes(w uint64) uint64 {
|
||||
return ((w - 0x0101010101010101) & (^w) & 0x8080808080808080)
|
||||
}
|
||||
|
||||
func setByte(w uint64, b uint8, idx int) uint64 {
|
||||
shift := idx << 3
|
||||
return (w &^ (0xff << shift)) | (uint64(b) << shift)
|
||||
}
|
||||
28
common/xsync/map_extra.go
Normal file
28
common/xsync/map_extra.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package xsync
|
||||
|
||||
// LoadOrStoreFn returns the existing value for the key if
|
||||
// present. Otherwise, it tries to compute the value using the
|
||||
// provided function and, if successful, stores and returns
|
||||
// the computed value. The loaded result is true if the value was
|
||||
// loaded, or false if computed.
|
||||
//
|
||||
// This call locks a hash table bucket while the compute function
|
||||
// is executed. It means that modifications on other entries in
|
||||
// the bucket will be blocked until the valueFn executes. Consider
|
||||
// this when the function includes long-running operations.
|
||||
//
|
||||
// Recovery this API and renamed from xsync/v3's LoadOrCompute.
|
||||
// We unneeded support no-op (cancel) compute operation, it will only add complexity to existing code.
|
||||
func (m *Map[K, V]) LoadOrStoreFn(key K, valueFn func() V) (actual V, loaded bool) {
|
||||
return m.doCompute(
|
||||
key,
|
||||
func(oldValue V, loaded bool) (V, ComputeOp) {
|
||||
if loaded {
|
||||
return oldValue, CancelOp
|
||||
}
|
||||
return valueFn(), UpdateOp
|
||||
},
|
||||
loadOrComputeOp,
|
||||
false,
|
||||
)
|
||||
}
|
||||
49
common/xsync/map_extra_test.go
Normal file
49
common/xsync/map_extra_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package xsync
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMapOfLoadOrStoreFn(t *testing.T) {
|
||||
const numEntries = 1000
|
||||
m := NewMap[string, int]()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
v, loaded := m.LoadOrStoreFn(strconv.Itoa(i), func() int {
|
||||
return i
|
||||
})
|
||||
if loaded {
|
||||
t.Fatalf("value not computed for %d", i)
|
||||
}
|
||||
if v != i {
|
||||
t.Fatalf("values do not match for %d: %v", i, v)
|
||||
}
|
||||
}
|
||||
for i := 0; i < numEntries; i++ {
|
||||
v, loaded := m.LoadOrStoreFn(strconv.Itoa(i), func() int {
|
||||
return i
|
||||
})
|
||||
if !loaded {
|
||||
t.Fatalf("value not loaded for %d", i)
|
||||
}
|
||||
if v != i {
|
||||
t.Fatalf("values do not match for %d: %v", i, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapOfLoadOrStoreFn_FunctionCalledOnce(t *testing.T) {
|
||||
m := NewMap[int, int]()
|
||||
for i := 0; i < 100; {
|
||||
m.LoadOrStoreFn(i, func() (v int) {
|
||||
v, i = i, i+1
|
||||
return v
|
||||
})
|
||||
}
|
||||
m.Range(func(k, v int) bool {
|
||||
if k != v {
|
||||
t.Fatalf("%dth key is not equal to value %d", k, v)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
1732
common/xsync/map_test.go
Normal file
1732
common/xsync/map_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -41,3 +41,11 @@ func NewAuthenticator(users []AuthUser) Authenticator {
|
||||
}
|
||||
return au
|
||||
}
|
||||
|
||||
var AlwaysValid Authenticator = alwaysValid{}
|
||||
|
||||
type alwaysValid struct{}
|
||||
|
||||
func (alwaysValid) Verify(string, string) bool { return true }
|
||||
|
||||
func (alwaysValid) Users() []string { return nil }
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package generater
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(newUUID.String())
|
||||
case "reality-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]))
|
||||
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]))
|
||||
case "wg-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKey.String())
|
||||
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||
case "ech-keypair":
|
||||
if len(args) < 2 {
|
||||
panic("Using: generate ech-keypair <plain_server_name>")
|
||||
}
|
||||
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Config:", configBase64)
|
||||
fmt.Println("Key:", keyPem)
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155
|
||||
|
||||
package generater
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
// KeyLen is the expected key length for a WireGuard key.
|
||||
const KeyLen = 32 // wgh.KeyLen
|
||||
|
||||
// A Key is a public, private, or pre-shared secret key. The Key constructor
|
||||
// functions in this package can be used to create Keys suitable for each of
|
||||
// these applications.
|
||||
type Key [KeyLen]byte
|
||||
|
||||
// GenerateKey generates a Key suitable for use as a pre-shared secret key from
|
||||
// a cryptographically safe source.
|
||||
//
|
||||
// The output Key should not be used as a private key; use GeneratePrivateKey
|
||||
// instead.
|
||||
func GenerateKey() (Key, error) {
|
||||
b := make([]byte, KeyLen)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates a Key suitable for use as a private key from a
|
||||
// cryptographically safe source.
|
||||
func GeneratePrivateKey() (Key, error) {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
key[0] &= 248
|
||||
key[31] &= 127
|
||||
key[31] |= 64
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// NewKey creates a Key from an existing byte slice. The byte slice must be
|
||||
// exactly 32 bytes in length.
|
||||
func NewKey(b []byte) (Key, error) {
|
||||
if len(b) != KeyLen {
|
||||
return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b))
|
||||
}
|
||||
|
||||
var k Key
|
||||
copy(k[:], b)
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// ParseKey parses a Key from a base64-encoded string, as produced by the
|
||||
// Key.String method.
|
||||
func ParseKey(s string) (Key, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// PublicKey computes a public key from the private key k.
|
||||
//
|
||||
// PublicKey should only be called when k is a private key.
|
||||
func (k Key) PublicKey() Key {
|
||||
var (
|
||||
pub [KeyLen]byte
|
||||
priv = [KeyLen]byte(k)
|
||||
)
|
||||
|
||||
// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
|
||||
// so no need to specify it.
|
||||
curve25519.ScalarBaseMult(&pub, &priv)
|
||||
|
||||
return Key(pub)
|
||||
}
|
||||
|
||||
// String returns the base64-encoded string representation of a Key.
|
||||
//
|
||||
// ParseKey can be used to produce a new Key from this string.
|
||||
func (k Key) String() string {
|
||||
return base64.StdEncoding.EncodeToString(k[:])
|
||||
}
|
||||
73
component/generator/cmd.go
Normal file
73
component/generator/cmd.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(newUUID.String())
|
||||
case "reality-keypair":
|
||||
privateKey, err := GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey.Bytes()))
|
||||
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes()))
|
||||
case "wg-keypair":
|
||||
privateKey, err := GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + base64.StdEncoding.EncodeToString(privateKey.Bytes()))
|
||||
fmt.Println("PublicKey: " + base64.StdEncoding.EncodeToString(privateKey.PublicKey().Bytes()))
|
||||
case "ech-keypair":
|
||||
if len(args) < 2 {
|
||||
panic("Using: generate ech-keypair <plain_server_name>")
|
||||
}
|
||||
configBase64, keyPem, err := ech.GenECHConfig(args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Config:", configBase64)
|
||||
fmt.Println("Key:", keyPem)
|
||||
case "vless-mlkem768":
|
||||
var seed string
|
||||
if len(args) > 1 {
|
||||
seed = args[1]
|
||||
}
|
||||
seedBase64, clientBase64, hash32Base64, err := encryption.GenMLKEM768(seed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("Seed: " + seedBase64)
|
||||
fmt.Println("Client: " + clientBase64)
|
||||
fmt.Println("Hash32: " + hash32Base64)
|
||||
case "vless-x25519":
|
||||
var privateKey string
|
||||
if len(args) > 1 {
|
||||
privateKey = args[1]
|
||||
}
|
||||
privateKeyBase64, passwordBase64, hash32Base64, err := encryption.GenX25519(privateKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKeyBase64)
|
||||
fmt.Println("Password: " + passwordBase64)
|
||||
fmt.Println("Hash32: " + hash32Base64)
|
||||
}
|
||||
}
|
||||
27
component/generator/x25519.go
Normal file
27
component/generator/x25519.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package generator
|
||||
|
||||
import (
|
||||
"crypto/ecdh"
|
||||
"crypto/rand"
|
||||
)
|
||||
|
||||
const X25519KeySize = 32
|
||||
|
||||
func GenX25519PrivateKey() (*ecdh.PrivateKey, error) {
|
||||
var privateKey [X25519KeySize]byte
|
||||
_, err := rand.Read(privateKey[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Avoid generating equivalent X25519 private keys
|
||||
// https://github.com/XTLS/Xray-core/pull/1747
|
||||
//
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
privateKey[0] &= 248
|
||||
privateKey[31] &= 127
|
||||
privateKey[31] |= 64
|
||||
|
||||
return ecdh.X25519().NewPrivateKey(privateKey[:])
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package geodata
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -76,13 +75,13 @@ func LoadGeoSiteMatcher(countryCode string) (router.DomainMatcher, error) {
|
||||
if countryCode[0] == '!' {
|
||||
not = true
|
||||
countryCode = countryCode[1:]
|
||||
if countryCode == "" {
|
||||
return nil, fmt.Errorf("country code could not be empty")
|
||||
}
|
||||
}
|
||||
countryCode = strings.ToLower(countryCode)
|
||||
|
||||
parts := strings.Split(countryCode, "@")
|
||||
if len(parts) == 0 {
|
||||
return nil, errors.New("empty rule")
|
||||
}
|
||||
listName := strings.TrimSpace(parts[0])
|
||||
attrVal := parts[1:]
|
||||
attrs := parseAttrs(attrVal)
|
||||
|
||||
@@ -10,27 +10,27 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
keepAliveIdle = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
keepAliveInterval = atomic.NewTypedValue[time.Duration](0 * time.Second)
|
||||
keepAliveIdle = atomic.NewInt64(0)
|
||||
keepAliveInterval = atomic.NewInt64(0)
|
||||
disableKeepAlive = atomic.NewBool(false)
|
||||
|
||||
SetDisableKeepAliveCallback = utils.NewCallback[bool]()
|
||||
)
|
||||
|
||||
func SetKeepAliveIdle(t time.Duration) {
|
||||
keepAliveIdle.Store(t)
|
||||
keepAliveIdle.Store(int64(t))
|
||||
}
|
||||
|
||||
func SetKeepAliveInterval(t time.Duration) {
|
||||
keepAliveInterval.Store(t)
|
||||
keepAliveInterval.Store(int64(t))
|
||||
}
|
||||
|
||||
func KeepAliveIdle() time.Duration {
|
||||
return keepAliveIdle.Load()
|
||||
return time.Duration(keepAliveIdle.Load())
|
||||
}
|
||||
|
||||
func KeepAliveInterval() time.Duration {
|
||||
return keepAliveInterval.Load()
|
||||
return time.Duration(keepAliveInterval.Load())
|
||||
}
|
||||
|
||||
func SetDisableKeepAlive(disable bool) {
|
||||
|
||||
@@ -8,11 +8,10 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
"github.com/metacubex/mihomo/common/xsync"
|
||||
"github.com/metacubex/mihomo/component/iface"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
||||
@@ -26,22 +25,19 @@ func init() {
|
||||
var ErrReject = errors.New("reject loopback connection")
|
||||
|
||||
type Detector struct {
|
||||
connMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
packetConnMap *xsync.MapOf[uint16, struct{}]
|
||||
connMap xsync.Map[netip.AddrPort, struct{}]
|
||||
packetConnMap xsync.Map[uint16, struct{}]
|
||||
}
|
||||
|
||||
func NewDetector() *Detector {
|
||||
if disableLoopBackDetector {
|
||||
return nil
|
||||
}
|
||||
return &Detector{
|
||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
|
||||
}
|
||||
return &Detector{}
|
||||
}
|
||||
|
||||
func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||
if l == nil || l.connMap == nil {
|
||||
if l == nil {
|
||||
return conn
|
||||
}
|
||||
metadata := C.Metadata{}
|
||||
@@ -59,7 +55,7 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||
}
|
||||
|
||||
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
if l == nil || l.packetConnMap == nil {
|
||||
if l == nil {
|
||||
return conn
|
||||
}
|
||||
metadata := C.Metadata{}
|
||||
@@ -78,7 +74,7 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
}
|
||||
|
||||
func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||
if l == nil || l.connMap == nil {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
@@ -92,7 +88,7 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||
}
|
||||
|
||||
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
|
||||
if l == nil || l.packetConnMap == nil {
|
||||
if l == nil {
|
||||
return nil
|
||||
}
|
||||
connAddr := metadata.SourceAddrPort()
|
||||
|
||||
@@ -4,27 +4,24 @@ import (
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/xsync"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
mapping *xsync.MapOf[string, *entry]
|
||||
mapping xsync.Map[string, *entry]
|
||||
}
|
||||
|
||||
type entry struct {
|
||||
PacketSender C.PacketSender
|
||||
LocalUDPConnMap *xsync.MapOf[string, *net.UDPConn]
|
||||
LocalLockMap *xsync.MapOf[string, *sync.Cond]
|
||||
LocalUDPConnMap xsync.Map[string, *net.UDPConn]
|
||||
LocalLockMap xsync.Map[string, *sync.Cond]
|
||||
}
|
||||
|
||||
func (t *Table) GetOrCreate(key string, maker func() C.PacketSender) (C.PacketSender, bool) {
|
||||
item, loaded := t.mapping.LoadOrCompute(key, func() *entry {
|
||||
item, loaded := t.mapping.LoadOrStoreFn(key, func() *entry {
|
||||
return &entry{
|
||||
PacketSender: maker(),
|
||||
LocalUDPConnMap: xsync.NewMapOf[string, *net.UDPConn](),
|
||||
LocalLockMap: xsync.NewMapOf[string, *sync.Cond](),
|
||||
PacketSender: maker(),
|
||||
}
|
||||
})
|
||||
return item.PacketSender, loaded
|
||||
@@ -68,7 +65,7 @@ func (t *Table) GetOrCreateLockForLocalConn(lAddr, key string) (*sync.Cond, bool
|
||||
if !loaded {
|
||||
return nil, false
|
||||
}
|
||||
item, loaded := entry.LocalLockMap.LoadOrCompute(key, makeLock)
|
||||
item, loaded := entry.LocalLockMap.LoadOrStoreFn(key, makeLock)
|
||||
return item, loaded
|
||||
}
|
||||
|
||||
@@ -98,7 +95,5 @@ func makeLock() *sync.Cond {
|
||||
|
||||
// New return *Cache
|
||||
func New() *Table {
|
||||
return &Table{
|
||||
mapping: xsync.NewMapOf[string, *entry](),
|
||||
}
|
||||
return &Table{}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,52 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
FindProcessAlways = "always"
|
||||
FindProcessStrict = "strict"
|
||||
FindProcessOff = "off"
|
||||
FindProcessStrict FindProcessMode = iota
|
||||
FindProcessAlways
|
||||
FindProcessOff
|
||||
)
|
||||
|
||||
var (
|
||||
validModes = map[string]struct{}{
|
||||
FindProcessAlways: {},
|
||||
FindProcessOff: {},
|
||||
FindProcessStrict: {},
|
||||
validModes = map[string]FindProcessMode{
|
||||
FindProcessStrict.String(): FindProcessStrict,
|
||||
FindProcessAlways.String(): FindProcessAlways,
|
||||
FindProcessOff.String(): FindProcessOff,
|
||||
}
|
||||
)
|
||||
|
||||
type FindProcessMode string
|
||||
type FindProcessMode int32
|
||||
|
||||
func (m FindProcessMode) Always() bool {
|
||||
return m == FindProcessAlways
|
||||
}
|
||||
|
||||
func (m FindProcessMode) Off() bool {
|
||||
return m == FindProcessOff
|
||||
}
|
||||
|
||||
func (m *FindProcessMode) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Set(tp)
|
||||
}
|
||||
|
||||
func (m *FindProcessMode) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
if err := json.Unmarshal(data, &tp); err != nil {
|
||||
return err
|
||||
}
|
||||
return m.Set(tp)
|
||||
// UnmarshalText unserialize FindProcessMode
|
||||
func (m *FindProcessMode) UnmarshalText(data []byte) error {
|
||||
return m.Set(string(data))
|
||||
}
|
||||
|
||||
func (m *FindProcessMode) Set(value string) error {
|
||||
mode := strings.ToLower(value)
|
||||
_, exist := validModes[mode]
|
||||
mode, exist := validModes[strings.ToLower(value)]
|
||||
if !exist {
|
||||
return errors.New("invalid find process mode")
|
||||
}
|
||||
*m = FindProcessMode(mode)
|
||||
*m = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalText serialize FindProcessMode
|
||||
func (m FindProcessMode) MarshalText() ([]byte, error) {
|
||||
return []byte(m.String()), nil
|
||||
}
|
||||
|
||||
func (m FindProcessMode) String() string {
|
||||
switch m {
|
||||
case FindProcessAlways:
|
||||
return "always"
|
||||
case FindProcessOff:
|
||||
return "off"
|
||||
default:
|
||||
return "strict"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func NewHostValueByDomain(domain string) (HostValue, error) {
|
||||
domain = strings.Trim(domain, ".")
|
||||
item := strings.Split(domain, ".")
|
||||
if len(item) < 2 {
|
||||
return HostValue{}, errors.New("invaild domain")
|
||||
return HostValue{}, errors.New("invalid domain")
|
||||
}
|
||||
return HostValue{
|
||||
IsDomain: true,
|
||||
|
||||
@@ -46,17 +46,24 @@ func RelayDnsConn(ctx context.Context, conn net.Conn, readTimeout time.Duration)
|
||||
ctx, cancel := context.WithTimeout(ctx, DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
inData := buff[:n]
|
||||
msg, err := relayDnsPacket(ctx, inData, buff, 0)
|
||||
outBuff := buff[2:]
|
||||
msg, err := relayDnsPacket(ctx, inData, outBuff, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = binary.Write(conn, binary.BigEndian, uint16(len(msg)))
|
||||
if err != nil {
|
||||
return err
|
||||
if &msg[0] == &outBuff[0] { // msg is still in the buff
|
||||
binary.BigEndian.PutUint16(buff[:2], uint16(len(msg)))
|
||||
outBuff = buff[:2+len(msg)]
|
||||
} else { // buff not big enough (WTF???)
|
||||
newBuff := pool.Get(len(msg) + 2)
|
||||
defer pool.Put(newBuff)
|
||||
binary.BigEndian.PutUint16(newBuff[:2], uint16(len(msg)))
|
||||
copy(newBuff[2:], msg)
|
||||
outBuff = newBuff
|
||||
}
|
||||
|
||||
_, err = conn.Write(msg)
|
||||
_, err = conn.Write(outBuff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -230,10 +230,18 @@ func ResolveECH(ctx context.Context, host string) ([]byte, error) {
|
||||
return ResolveECHWithResolver(ctx, host, DefaultResolver)
|
||||
}
|
||||
|
||||
func ClearCache() {
|
||||
if DefaultResolver != nil {
|
||||
go DefaultResolver.ClearCache()
|
||||
}
|
||||
go SystemResolver.ClearCache() // SystemResolver unneeded check nil
|
||||
}
|
||||
|
||||
func ResetConnection() {
|
||||
if DefaultResolver != nil {
|
||||
go DefaultResolver.ResetConnection()
|
||||
}
|
||||
go SystemResolver.ResetConnection() // SystemResolver unneeded check nil
|
||||
}
|
||||
|
||||
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/sing/common/metadata"
|
||||
|
||||
"github.com/metacubex/mihomo/common/lru"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
@@ -164,6 +166,9 @@ func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) domainCanReplace(host string) bool {
|
||||
if host == "." || !metadata.IsDomainName(host) {
|
||||
return false
|
||||
}
|
||||
for _, matcher := range sd.skipDomain {
|
||||
if matcher.MatchDomain(host) {
|
||||
return false
|
||||
|
||||
@@ -85,6 +85,9 @@ func GetRealityConn(ctx context.Context, conn net.Conn, fingerprint UClientHello
|
||||
continue // retry
|
||||
}
|
||||
ecdheKey := keyShareKeys.Ecdhe
|
||||
if ecdheKey == nil {
|
||||
ecdheKey = keyShareKeys.MlkemEcdhe
|
||||
}
|
||||
if ecdheKey == nil {
|
||||
// WTF???
|
||||
if retry > 2 {
|
||||
@@ -167,6 +170,7 @@ type realityVerifier struct {
|
||||
//var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
|
||||
|
||||
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
|
||||
log.Debugln("REALITY localAddr: %v is using X25519MLKEM768 for TLS' communication: %v", c.RemoteAddr(), c.HandshakeState.ServerHello.ServerShare.Group == utls.X25519MLKEM768)
|
||||
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
|
||||
//certs := *(*[]*x509.Certificate)(unsafe.Add(unsafe.Pointer(c.Conn), pOffset))
|
||||
certs := c.Conn.PeerCertificates()
|
||||
|
||||
@@ -17,74 +17,99 @@ import (
|
||||
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"github.com/klauspost/cpuid/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
baseReleaseURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/"
|
||||
versionReleaseURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt"
|
||||
|
||||
baseAlphaURL = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/"
|
||||
versionAlphaURL = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt"
|
||||
|
||||
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
||||
// package whose size is limited by this constant currently has the size of
|
||||
// approximately 32 MiB.
|
||||
MaxPackageFileSize = 32 * 1024 * 1024
|
||||
)
|
||||
|
||||
const (
|
||||
ReleaseChannel = "release"
|
||||
AlphaChannel = "alpha"
|
||||
)
|
||||
|
||||
// CoreUpdater is the mihomo updater.
|
||||
// modify from https://github.com/AdguardTeam/AdGuardHome/blob/595484e0b3fb4c457f9bb727a6b94faa78a66c5f/internal/updater/updater.go
|
||||
// Updater is the mihomo updater.
|
||||
var (
|
||||
goarm string
|
||||
gomips string
|
||||
amd64Compatible string
|
||||
|
||||
workDir string
|
||||
|
||||
// mu protects all fields below.
|
||||
type CoreUpdater struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
currentExeName string // 当前可执行文件
|
||||
updateDir string // 更新目录
|
||||
packageName string // 更新压缩文件
|
||||
backupDir string // 备份目录
|
||||
backupExeName string // 备份文件名
|
||||
updateExeName string // 更新后的可执行文件
|
||||
var DefaultCoreUpdater = CoreUpdater{}
|
||||
|
||||
baseURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/mihomo"
|
||||
versionURL string = "https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt"
|
||||
packageURL string
|
||||
latestVersion string
|
||||
)
|
||||
|
||||
func init() {
|
||||
if runtime.GOARCH == "amd64" && cpuid.CPU.X64Level() < 3 {
|
||||
amd64Compatible = "-compatible"
|
||||
}
|
||||
if !strings.HasPrefix(C.Version, "alpha") {
|
||||
baseURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/mihomo"
|
||||
versionURL = "https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt"
|
||||
func (u *CoreUpdater) CoreBaseName() string {
|
||||
switch runtime.GOARCH {
|
||||
case "arm":
|
||||
// mihomo-linux-armv5
|
||||
return fmt.Sprintf("mihomo-%s-%sv%s", runtime.GOOS, runtime.GOARCH, features.GOARM)
|
||||
case "arm64":
|
||||
if runtime.GOOS == "android" {
|
||||
// mihomo-android-arm64-v8
|
||||
return fmt.Sprintf("mihomo-%s-%s-v8", runtime.GOOS, runtime.GOARCH)
|
||||
} else {
|
||||
// mihomo-linux-arm64
|
||||
return fmt.Sprintf("mihomo-%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
case "mips", "mipsle":
|
||||
// mihomo-linux-mips-hardfloat
|
||||
return fmt.Sprintf("mihomo-%s-%s-%s", runtime.GOOS, runtime.GOARCH, features.GOMIPS)
|
||||
case "amd64":
|
||||
// mihomo-linux-amd64-v1
|
||||
return fmt.Sprintf("mihomo-%s-%s-%s", runtime.GOOS, runtime.GOARCH, features.GOAMD64)
|
||||
default:
|
||||
// mihomo-linux-386
|
||||
// mihomo-linux-mips64
|
||||
// mihomo-linux-riscv64
|
||||
// mihomo-linux-s390x
|
||||
return fmt.Sprintf("mihomo-%s-%s", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
type updateError struct {
|
||||
Message string
|
||||
}
|
||||
func (u *CoreUpdater) Update(currentExePath string, channel string, force bool) (err error) {
|
||||
u.mu.Lock()
|
||||
defer u.mu.Unlock()
|
||||
|
||||
func (e *updateError) Error() string {
|
||||
return fmt.Sprintf("update error: %s", e.Message)
|
||||
}
|
||||
|
||||
// Update performs the auto-updater. It returns an error if the updater failed.
|
||||
// If firstRun is true, it assumes the configuration file doesn't exist.
|
||||
func UpdateCore(execPath string) (err error) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
latestVersion, err = getLatestVersion()
|
||||
info, err := os.Stat(currentExePath)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("check currentExePath %q: %w", currentExePath, err)
|
||||
}
|
||||
|
||||
baseURL := baseAlphaURL
|
||||
versionURL := versionAlphaURL
|
||||
switch strings.ToLower(channel) {
|
||||
case ReleaseChannel:
|
||||
baseURL = baseReleaseURL
|
||||
versionURL = versionReleaseURL
|
||||
case AlphaChannel:
|
||||
break
|
||||
default: // auto
|
||||
if !strings.HasPrefix(C.Version, "alpha") {
|
||||
baseURL = baseReleaseURL
|
||||
versionURL = versionReleaseURL
|
||||
}
|
||||
}
|
||||
|
||||
latestVersion, err := u.getLatestVersion(versionURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get latest version: %w", err)
|
||||
}
|
||||
log.Infoln("current version %s, latest version %s", C.Version, latestVersion)
|
||||
|
||||
if latestVersion == C.Version {
|
||||
err := &updateError{Message: "already using latest version"}
|
||||
return err
|
||||
if latestVersion == C.Version && !force {
|
||||
// don't change this output, some downstream dependencies on the upgrader's output fields
|
||||
return fmt.Errorf("update error: already using latest version %s", C.Version)
|
||||
}
|
||||
|
||||
updateDownloadURL()
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.Errorln("updater: failed: %v", err)
|
||||
@@ -93,31 +118,49 @@ func UpdateCore(execPath string) (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
workDir = filepath.Dir(execPath)
|
||||
// ---- prepare ----
|
||||
mihomoBaseName := u.CoreBaseName()
|
||||
packageName := mihomoBaseName + "-" + latestVersion
|
||||
if runtime.GOOS == "windows" {
|
||||
packageName = packageName + ".zip"
|
||||
} else {
|
||||
packageName = packageName + ".gz"
|
||||
}
|
||||
packageURL := baseURL + packageName
|
||||
log.Infoln("updater: updating using url: %s", packageURL)
|
||||
|
||||
err = prepare(execPath)
|
||||
workDir := filepath.Dir(currentExePath)
|
||||
backupDir := filepath.Join(workDir, "meta-backup")
|
||||
updateDir := filepath.Join(workDir, "meta-update")
|
||||
packagePath := filepath.Join(updateDir, packageName)
|
||||
//log.Infoln(packagePath)
|
||||
|
||||
updateExeName := mihomoBaseName
|
||||
if runtime.GOOS == "windows" {
|
||||
updateExeName = updateExeName + ".exe"
|
||||
}
|
||||
log.Infoln("updateExeName: %s", updateExeName)
|
||||
updateExePath := filepath.Join(updateDir, updateExeName)
|
||||
backupExePath := filepath.Join(backupDir, filepath.Base(currentExePath))
|
||||
|
||||
defer u.clean(updateDir)
|
||||
|
||||
err = u.download(updateDir, packagePath, packageURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("preparing: %w", err)
|
||||
return fmt.Errorf("downloading: %w", err)
|
||||
}
|
||||
|
||||
defer clean()
|
||||
|
||||
err = downloadPackageFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("downloading package file: %w", err)
|
||||
}
|
||||
|
||||
err = unpack()
|
||||
err = u.unpack(updateDir, packagePath, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("unpacking: %w", err)
|
||||
}
|
||||
|
||||
err = backup()
|
||||
err = u.backup(currentExePath, backupExePath, backupDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("backuping: %w", err)
|
||||
}
|
||||
|
||||
err = replace()
|
||||
err = u.copyFile(updateExePath, currentExePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("replacing: %w", err)
|
||||
}
|
||||
@@ -125,116 +168,30 @@ func UpdateCore(execPath string) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prepare fills all necessary fields in Updater object.
|
||||
func prepare(exePath string) (err error) {
|
||||
updateDir = filepath.Join(workDir, "meta-update")
|
||||
currentExeName = exePath
|
||||
_, pkgNameOnly := filepath.Split(packageURL)
|
||||
if pkgNameOnly == "" {
|
||||
return fmt.Errorf("invalid PackageURL: %q", packageURL)
|
||||
}
|
||||
|
||||
packageName = filepath.Join(updateDir, pkgNameOnly)
|
||||
//log.Infoln(packageName)
|
||||
backupDir = filepath.Join(workDir, "meta-backup")
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible + ".exe"
|
||||
} else if runtime.GOOS == "android" && runtime.GOARCH == "arm64" {
|
||||
updateExeName = "mihomo-android-arm64-v8"
|
||||
} else {
|
||||
updateExeName = "mihomo" + "-" + runtime.GOOS + "-" + runtime.GOARCH + amd64Compatible
|
||||
}
|
||||
|
||||
log.Infoln("updateExeName: %s ", updateExeName)
|
||||
|
||||
backupExeName = filepath.Join(backupDir, filepath.Base(exePath))
|
||||
updateExeName = filepath.Join(updateDir, updateExeName)
|
||||
|
||||
log.Infoln(
|
||||
"updater: updating using url: %s",
|
||||
packageURL,
|
||||
)
|
||||
|
||||
currentExeName = exePath
|
||||
_, err = os.Stat(currentExeName)
|
||||
func (u *CoreUpdater) getLatestVersion(versionURL string) (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("checking %q: %w", currentExeName, err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unpack extracts the files from the downloaded archive.
|
||||
func unpack() error {
|
||||
var err error
|
||||
_, pkgNameOnly := filepath.Split(packageURL)
|
||||
|
||||
log.Infoln("updater: unpacking package")
|
||||
if strings.HasSuffix(pkgNameOnly, ".zip") {
|
||||
_, err = zipFileUnpack(packageName, updateDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".zip unpack failed: %w", err)
|
||||
defer func() {
|
||||
closeErr := resp.Body.Close()
|
||||
if closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
} else if strings.HasSuffix(pkgNameOnly, ".gz") {
|
||||
_, err = gzFileUnpack(packageName, updateDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".gz unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown package extension")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backup makes a backup of the current executable file
|
||||
func backup() (err error) {
|
||||
log.Infoln("updater: backing up current ExecFile:%s to %s", currentExeName, backupExeName)
|
||||
_ = os.Mkdir(backupDir, 0o755)
|
||||
|
||||
err = os.Rename(currentExeName, backupExeName)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nil
|
||||
content := strings.TrimRight(string(body), "\n")
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// replace moves the current executable with the updated one
|
||||
func replace() error {
|
||||
var err error
|
||||
|
||||
log.Infoln("replacing: %s to %s", updateExeName, currentExeName)
|
||||
if runtime.GOOS == "windows" {
|
||||
// rename fails with "File in use" error
|
||||
err = copyFile(updateExeName, currentExeName)
|
||||
} else {
|
||||
err = os.Rename(updateExeName, currentExeName)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infoln("updater: renamed: %s to %s", updateExeName, currentExeName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean removes the temporary directory itself and all it's contents.
|
||||
func clean() {
|
||||
_ = os.RemoveAll(updateDir)
|
||||
}
|
||||
|
||||
// MaxPackageFileSize is a maximum package file length in bytes. The largest
|
||||
// package whose size is limited by this constant currently has the size of
|
||||
// approximately 32 MiB.
|
||||
const MaxPackageFileSize = 32 * 1024 * 1024
|
||||
|
||||
// Download package file and save it to disk
|
||||
func downloadPackageFile() (err error) {
|
||||
// download package file and save it to disk
|
||||
func (u *CoreUpdater) download(updateDir, packagePath, packageURL string) (err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, packageURL, http.MethodGet, nil, nil)
|
||||
@@ -249,38 +206,95 @@ func downloadPackageFile() (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
var r io.Reader
|
||||
r, err = LimitReader(resp.Body, MaxPackageFileSize)
|
||||
if err != nil {
|
||||
return fmt.Errorf("http request failed: %w", err)
|
||||
}
|
||||
|
||||
log.Debugln("updater: reading http body")
|
||||
// This use of ReadAll is now safe, because we limited body's Reader.
|
||||
body, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.ReadAll() failed: %w", err)
|
||||
}
|
||||
|
||||
log.Debugln("updateDir %s", updateDir)
|
||||
err = os.Mkdir(updateDir, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mkdir error: %w", err)
|
||||
}
|
||||
|
||||
log.Debugln("updater: saving package to file %s", packageName)
|
||||
err = os.WriteFile(packageName, body, 0o644)
|
||||
log.Debugln("updater: saving package to file %s", packagePath)
|
||||
// Create the output file
|
||||
wc, err := os.OpenFile(packagePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.WriteFile() failed: %w", err)
|
||||
return fmt.Errorf("os.OpenFile(%s): %w", packagePath, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := wc.Close()
|
||||
if closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debugln("updater: reading http body")
|
||||
// This use of io.Copy is now safe, because we limited body's Reader.
|
||||
n, err := io.Copy(wc, io.LimitReader(resp.Body, MaxPackageFileSize))
|
||||
if err != nil {
|
||||
return fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
if n == MaxPackageFileSize {
|
||||
// Use whether n is equal to MaxPackageFileSize to determine whether the limit has been reached.
|
||||
// It is also possible that the size of the downloaded file is exactly the same as the maximum limit,
|
||||
// but we should not consider this too rare situation.
|
||||
return fmt.Errorf("attempted to read more than %d bytes", MaxPackageFileSize)
|
||||
}
|
||||
log.Debugln("updater: downloaded package to file %s", packagePath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// unpack extracts the files from the downloaded archive.
|
||||
func (u *CoreUpdater) unpack(updateDir, packagePath string, fileMode os.FileMode) error {
|
||||
log.Infoln("updater: unpacking package")
|
||||
if strings.HasSuffix(packagePath, ".zip") {
|
||||
_, err := u.zipFileUnpack(packagePath, updateDir, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".zip unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(packagePath, ".gz") {
|
||||
_, err := u.gzFileUnpack(packagePath, updateDir, fileMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf(".gz unpack failed: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
return fmt.Errorf("unknown package extension")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backup creates a backup of the current executable file.
|
||||
func (u *CoreUpdater) backup(currentExePath, backupExePath, backupDir string) (err error) {
|
||||
log.Infoln("updater: backing up current ExecFile:%s to %s", currentExePath, backupExePath)
|
||||
_ = os.Mkdir(backupDir, 0o755)
|
||||
|
||||
// On Windows, since the running executable cannot be overwritten or deleted, it uses os.Rename to move the file to the backup path.
|
||||
// On other platforms, it copies the file to the backup path, preserving the original file and its permissions.
|
||||
// The backup directory is created if it does not exist.
|
||||
if runtime.GOOS == "windows" {
|
||||
err = os.Rename(currentExePath, backupExePath)
|
||||
} else {
|
||||
err = u.copyFile(currentExePath, backupExePath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clean removes the temporary directory itself and all it's contents.
|
||||
func (u *CoreUpdater) clean(updateDir string) {
|
||||
_ = os.RemoveAll(updateDir)
|
||||
}
|
||||
|
||||
// Unpack a single .gz file to the specified directory
|
||||
// Existing files are overwritten
|
||||
// All files are created inside outDir, subdirectories are not created
|
||||
// Return the output file name
|
||||
func gzFileUnpack(gzfile, outDir string) (string, error) {
|
||||
func (u *CoreUpdater) gzFileUnpack(gzfile, outDir string, fileMode os.FileMode) (outputName string, err error) {
|
||||
f, err := os.Open(gzfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.Open(): %w", err)
|
||||
@@ -312,14 +326,10 @@ func gzFileUnpack(gzfile, outDir string) (string, error) {
|
||||
originalName = strings.TrimSuffix(originalName, ".gz")
|
||||
}
|
||||
|
||||
outputName := filepath.Join(outDir, originalName)
|
||||
outputName = filepath.Join(outDir, originalName)
|
||||
|
||||
// Create the output file
|
||||
wc, err := os.OpenFile(
|
||||
outputName,
|
||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
||||
0o755,
|
||||
)
|
||||
wc, err := os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.OpenFile(%s): %w", outputName, err)
|
||||
}
|
||||
@@ -344,7 +354,7 @@ func gzFileUnpack(gzfile, outDir string) (string, error) {
|
||||
// Existing files are overwritten
|
||||
// All files are created inside 'outDir', subdirectories are not created
|
||||
// Return the output file name
|
||||
func zipFileUnpack(zipfile, outDir string) (string, error) {
|
||||
func (u *CoreUpdater) zipFileUnpack(zipfile, outDir string, fileMode os.FileMode) (outputName string, err error) {
|
||||
zrc, err := zip.OpenReader(zipfile)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("zip.OpenReader(): %w", err)
|
||||
@@ -376,14 +386,14 @@ func zipFileUnpack(zipfile, outDir string) (string, error) {
|
||||
}()
|
||||
fi := zf.FileInfo()
|
||||
name := fi.Name()
|
||||
outputName := filepath.Join(outDir, name)
|
||||
outputName = filepath.Join(outDir, name)
|
||||
|
||||
if fi.IsDir() {
|
||||
return "", fmt.Errorf("the target file is a directory")
|
||||
}
|
||||
|
||||
var wc io.WriteCloser
|
||||
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode())
|
||||
wc, err = os.OpenFile(outputName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fileMode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("os.OpenFile(): %w", err)
|
||||
}
|
||||
@@ -403,97 +413,60 @@ func zipFileUnpack(zipfile, outDir string) (string, error) {
|
||||
}
|
||||
|
||||
// Copy file on disk
|
||||
func copyFile(src, dst string) error {
|
||||
d, e := os.ReadFile(src)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
e = os.WriteFile(dst, d, 0o644)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getLatestVersion() (version string, err error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, versionURL, http.MethodGet, nil, nil)
|
||||
func (u *CoreUpdater) copyFile(src, dst string) (err error) {
|
||||
rc, err := os.Open(src)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Latest Version fail: %w", err)
|
||||
return fmt.Errorf("os.Open(%s): %w", src, err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
closeErr := resp.Body.Close()
|
||||
closeErr := rc.Close()
|
||||
if closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
info, err := rc.Stat()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get Latest Version fail: %w", err)
|
||||
return fmt.Errorf("rc.Stat(): %w", err)
|
||||
}
|
||||
content := strings.TrimRight(string(body), "\n")
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func updateDownloadURL() {
|
||||
var middle string
|
||||
|
||||
if runtime.GOARCH == "arm" && probeGoARM() {
|
||||
//-linux-armv7-alpha-e552b54.gz
|
||||
middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, goarm, latestVersion)
|
||||
} else if runtime.GOARCH == "arm64" {
|
||||
//-linux-arm64-alpha-e552b54.gz
|
||||
if runtime.GOOS == "android" {
|
||||
middle = fmt.Sprintf("-%s-%s-v8-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
|
||||
} else {
|
||||
middle = fmt.Sprintf("-%s-%s-%s", runtime.GOOS, runtime.GOARCH, latestVersion)
|
||||
// Create the output file
|
||||
// If the file does not exist, creates it with permissions perm (before umask);
|
||||
// otherwise truncates it before writing, without changing permissions.
|
||||
wc, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
// On some file system (such as Android's /data) maybe return error: "text file busy"
|
||||
// Let's delete the target file and recreate it
|
||||
err = os.Remove(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.Remove(%s): %w", dst, err)
|
||||
}
|
||||
wc, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, info.Mode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("os.OpenFile(%s): %w", dst, err)
|
||||
}
|
||||
} else if isMIPS(runtime.GOARCH) && gomips != "" {
|
||||
middle = fmt.Sprintf("-%s-%s-%s-%s", runtime.GOOS, runtime.GOARCH, gomips, latestVersion)
|
||||
} else {
|
||||
middle = fmt.Sprintf("-%s-%s%s-%s", runtime.GOOS, runtime.GOARCH, amd64Compatible, latestVersion)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
middle += ".zip"
|
||||
} else {
|
||||
middle += ".gz"
|
||||
}
|
||||
packageURL = baseURL + middle
|
||||
//log.Infoln(packageURL)
|
||||
}
|
||||
defer func() {
|
||||
closeErr := wc.Close()
|
||||
if closeErr != nil && err == nil {
|
||||
err = closeErr
|
||||
}
|
||||
}()
|
||||
|
||||
// isMIPS returns true if arch is any MIPS architecture.
|
||||
func isMIPS(arch string) (ok bool) {
|
||||
switch arch {
|
||||
case
|
||||
"mips",
|
||||
"mips64",
|
||||
"mips64le",
|
||||
"mipsle":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// linux only
|
||||
func probeGoARM() (ok bool) {
|
||||
cmd := exec.Command("cat", "/proc/cpuinfo")
|
||||
output, err := cmd.Output()
|
||||
_, err = io.Copy(wc, rc)
|
||||
if err != nil {
|
||||
log.Errorln("probe goarm error:%s", err)
|
||||
return false
|
||||
return fmt.Errorf("io.Copy(): %w", err)
|
||||
}
|
||||
cpuInfo := string(output)
|
||||
if strings.Contains(cpuInfo, "vfpv3") || strings.Contains(cpuInfo, "vfpv4") {
|
||||
goarm = "v7"
|
||||
} else if strings.Contains(cpuInfo, "vfp") {
|
||||
goarm = "v6"
|
||||
} else {
|
||||
goarm = "v5"
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
err = exec.Command("/usr/bin/codesign", "--sign", "-", dst).Run()
|
||||
if err != nil {
|
||||
log.Warnln("codesign failed: %v", err)
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
log.Infoln("updater: copy: %s to %s", src, dst)
|
||||
return nil
|
||||
}
|
||||
|
||||
10
component/updater/update_core_test.go
Normal file
10
component/updater/update_core_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package updater
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCoreBaseName(t *testing.T) {
|
||||
fmt.Println("Core base name =", DefaultCoreUpdater.CoreBaseName())
|
||||
}
|
||||
@@ -212,7 +212,7 @@ func UpdateGeoDatabases() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUpdateTime() (err error, time time.Time) {
|
||||
func getUpdateTime() (time time.Time, err error) {
|
||||
filesToCheck := []string{
|
||||
C.Path.GeoIP(),
|
||||
C.Path.MMDB(),
|
||||
@@ -224,7 +224,7 @@ func getUpdateTime() (err error, time time.Time) {
|
||||
var fileInfo os.FileInfo
|
||||
fileInfo, err = os.Stat(file)
|
||||
if err == nil {
|
||||
return nil, fileInfo.ModTime()
|
||||
return fileInfo.ModTime(), nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +241,7 @@ func RegisterGeoUpdater() {
|
||||
ticker := time.NewTicker(time.Duration(updateInterval) * time.Hour)
|
||||
defer ticker.Stop()
|
||||
|
||||
err, lastUpdate := getUpdateTime()
|
||||
lastUpdate, err := getUpdateTime()
|
||||
if err != nil {
|
||||
log.Errorln("[GEO] Get GEO database update time error: %s", err.Error())
|
||||
return
|
||||
|
||||
@@ -182,7 +182,7 @@ func unzip(data []byte, dest string) error {
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode().Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -209,9 +209,6 @@ func untgz(data []byte, dest string) error {
|
||||
|
||||
tr := tar.NewReader(gzr)
|
||||
|
||||
_ = gzr.Reset(bytes.NewReader(data))
|
||||
tr = tar.NewReader(gzr)
|
||||
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
@@ -236,7 +233,7 @@ func untgz(data []byte, dest string) error {
|
||||
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
|
||||
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode).Perm())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,15 +2,12 @@ package updater
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
const defaultHttpTimeout = time.Second * 90
|
||||
@@ -30,62 +27,3 @@ func downloadForBytes(url string) ([]byte, error) {
|
||||
func saveFile(bytes []byte, path string) error {
|
||||
return os.WriteFile(path, bytes, 0o644)
|
||||
}
|
||||
|
||||
// LimitReachedError records the limit and the operation that caused it.
|
||||
type LimitReachedError struct {
|
||||
Limit int64
|
||||
}
|
||||
|
||||
// Error implements the [error] interface for *LimitReachedError.
|
||||
//
|
||||
// TODO(a.garipov): Think about error string format.
|
||||
func (lre *LimitReachedError) Error() string {
|
||||
return fmt.Sprintf("attempted to read more than %d bytes", lre.Limit)
|
||||
}
|
||||
|
||||
// limitedReader is a wrapper for [io.Reader] limiting the input and dealing
|
||||
// with errors package.
|
||||
type limitedReader struct {
|
||||
r io.Reader
|
||||
limit int64
|
||||
n int64
|
||||
}
|
||||
|
||||
// Read implements the [io.Reader] interface.
|
||||
func (lr *limitedReader) Read(p []byte) (n int, err error) {
|
||||
if lr.n == 0 {
|
||||
return 0, &LimitReachedError{
|
||||
Limit: lr.limit,
|
||||
}
|
||||
}
|
||||
|
||||
p = p[:Min(lr.n, int64(len(p)))]
|
||||
|
||||
n, err = lr.r.Read(p)
|
||||
lr.n -= int64(n)
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
// LimitReader wraps Reader to make it's Reader stop with ErrLimitReached after
|
||||
// n bytes read.
|
||||
func LimitReader(r io.Reader, n int64) (limited io.Reader, err error) {
|
||||
if n < 0 {
|
||||
return nil, &updateError{Message: "limit must be non-negative"}
|
||||
}
|
||||
|
||||
return &limitedReader{
|
||||
r: r,
|
||||
limit: n,
|
||||
n: n,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Min returns the smaller of x or y.
|
||||
func Min[T constraints.Integer | ~string](x, y T) (res T) {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
|
||||
return y
|
||||
}
|
||||
|
||||
84
component/wildcard/wildcard.go
Normal file
84
component/wildcard/wildcard.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Package wildcard modified IGLOU-EU/go-wildcard to support:
|
||||
//
|
||||
// `*` matches zero or more characters
|
||||
// `?` matches exactly one character
|
||||
//
|
||||
// The original go-wildcard library used `.` to match exactly one character, and `?` to match zero or one character.
|
||||
// `.` is a valid delimiter in domain name matching and should not be used as a wildcard.
|
||||
// The `?` matching logic strictly matches only one character in most scenarios.
|
||||
// So, the `?` matching logic in the original go-wildcard library has been removed and its wildcard `.` has been replaced with `?`.
|
||||
package wildcard
|
||||
|
||||
// copy and modified from https://github.com/IGLOU-EU/go-wildcard/tree/ce22b7af48e487517a492d3727d9386492043e21
|
||||
// which is licensed under OpenBSD's ISC-style license.
|
||||
// Copyright (c) 2023 Iglou.eu contact@iglou.eu Copyright (c) 2023 Adrien Kara adrien@iglou.eu
|
||||
|
||||
func Match(pattern, s string) bool {
|
||||
if pattern == "" {
|
||||
return s == pattern
|
||||
}
|
||||
if pattern == "*" || s == pattern {
|
||||
return true
|
||||
}
|
||||
|
||||
return matchByString(pattern, s)
|
||||
}
|
||||
|
||||
func matchByString(pattern, s string) bool {
|
||||
var patternIndex, sIndex, lastStar int
|
||||
patternLen := len(pattern)
|
||||
sLen := len(s)
|
||||
star := -1
|
||||
|
||||
Loop:
|
||||
if sIndex >= sLen {
|
||||
goto checkPattern
|
||||
}
|
||||
|
||||
if patternIndex >= patternLen {
|
||||
if star != -1 {
|
||||
patternIndex = star + 1
|
||||
lastStar++
|
||||
sIndex = lastStar
|
||||
goto Loop
|
||||
}
|
||||
return false
|
||||
}
|
||||
switch pattern[patternIndex] {
|
||||
case '?':
|
||||
// It matches any single character. So, we don't need to check anything.
|
||||
case '*':
|
||||
// '*' matches zero or more characters. Store its position and increment the pattern index.
|
||||
star = patternIndex
|
||||
lastStar = sIndex
|
||||
patternIndex++
|
||||
goto Loop
|
||||
default:
|
||||
// If the characters don't match, check if there was a previous '*' to backtrack.
|
||||
if pattern[patternIndex] != s[sIndex] {
|
||||
if star != -1 {
|
||||
patternIndex = star + 1
|
||||
lastStar++
|
||||
sIndex = lastStar
|
||||
goto Loop
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
patternIndex++
|
||||
sIndex++
|
||||
goto Loop
|
||||
|
||||
// Check if the remaining pattern characters are '*', which can match the end of the string.
|
||||
checkPattern:
|
||||
if patternIndex < patternLen {
|
||||
if pattern[patternIndex] == '*' {
|
||||
patternIndex++
|
||||
goto checkPattern
|
||||
}
|
||||
}
|
||||
|
||||
return patternIndex == patternLen
|
||||
}
|
||||
192
component/wildcard/wildcard_test.go
Normal file
192
component/wildcard/wildcard_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package wildcard
|
||||
|
||||
/*
|
||||
* copy and modified from https://github.com/IGLOU-EU/go-wildcard/tree/ce22b7af48e487517a492d3727d9386492043e21
|
||||
*
|
||||
* Copyright (c) 2023 Iglou.eu <contact@iglou.eu>
|
||||
* Copyright (c) 2023 Adrien Kara <adrien@iglou.eu>
|
||||
*
|
||||
* Licensed under the BSD 3-Clause License,
|
||||
*/
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMatch validates the logic of wild card matching,
|
||||
// it need to support '*', '?' and only validate for byte comparison
|
||||
// over string, not rune or grapheme cluster
|
||||
func TestMatch(t *testing.T) {
|
||||
cases := []struct {
|
||||
s string
|
||||
pattern string
|
||||
result bool
|
||||
}{
|
||||
{"", "", true},
|
||||
{"", "*", true},
|
||||
{"", "**", true},
|
||||
{"", "?", false},
|
||||
{"", "?*", false},
|
||||
{"", "*?", false},
|
||||
|
||||
{"a", "", false},
|
||||
{"a", "a", true},
|
||||
{"a", "*", true},
|
||||
{"a", "**", true},
|
||||
{"a", "?", true},
|
||||
{"a", "?*", true},
|
||||
{"a", "*?", true},
|
||||
|
||||
{"match the exact string", "match the exact string", true},
|
||||
{"do not match a different string", "this is a different string", false},
|
||||
{"Match The Exact String WITH DIFFERENT CASE", "Match The Exact String WITH DIFFERENT CASE", true},
|
||||
{"do not match a different string WITH DIFFERENT CASE", "this is a different string WITH DIFFERENT CASE", false},
|
||||
{"Do Not Match The Exact String With Different Case", "do not match the exact string with different case", false},
|
||||
{"match an emoji 😃", "match an emoji 😃", true},
|
||||
{"do not match because of different emoji 😃", "do not match because of different emoji 😄", false},
|
||||
{"🌅☕️📰👨💼👩💼🏢🖥️💼💻📊📈📉👨👩👧👦🍝🕰️💪🏋️♂️🏋️♀️🏋️♂️💼🚴♂️🚴♀️🚴♂️🛀💤🌃", "🌅☕️📰👨💼👩💼🏢🖥️💼💻📊📈📉👨👩👧👦🍝🕰️💪🏋️♂️🏋️♀️🏋️♂️💼🚴♂️🚴♀️🚴♂️🛀💤🌃", true},
|
||||
{"🌅☕️📰👨💼👩💼🏢🖥️💼💻📊📈📉👨👩👧👦🍝🕰️💪🏋️♂️🏋️♀️🏋️♂️💼🚴♂️🚴♀️🚴♂️🛀💤🌃", "🦌🐇🦡🐿️🌲🌳🏰🌳🌲🌞🌧️❄️🌬️⛈️🔥🎄🎅🎁🎉🎊🥳👨👩👧👦💏👪💖👩💼🛀", false},
|
||||
|
||||
{"match a string with a *", "match a string *", true},
|
||||
{"match a string with a * at the beginning", "* at the beginning", true},
|
||||
{"match a string with two *", "match * with *", true},
|
||||
{"do not match a string with extra and a *", "do not match a string * with more", false},
|
||||
|
||||
{"match a string with a ?", "match ? string with a ?", true},
|
||||
{"match a string with a ? at the beginning", "?atch a string with a ? at the beginning", true},
|
||||
{"match a string with two ?", "match a ??ring with two ?", true},
|
||||
{"do not match a string with extra ?", "do not match a string with extra ??", false},
|
||||
|
||||
{"abc.edf.hjg", "abc.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab.cedf.hjg", false},
|
||||
{"abc.edf.hjg", "abc.edfh.jg", false},
|
||||
{"abc.edf.hjg", "abc.edf.hjq", false},
|
||||
|
||||
{"abc.edf.hjg", "abc.*.hjg", true},
|
||||
{"abc.edf.hjg", "abc.*.hjq", false},
|
||||
{"abc.edf.hjg", "abc*hjg", true},
|
||||
{"abc.edf.hjg", "abc*hjq", false},
|
||||
{"abc.edf.hjg", "a*g", true},
|
||||
{"abc.edf.hjg", "a*q", false},
|
||||
|
||||
{"abc.edf.hjg", "ab?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "?b?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "??c.edf.hjg", true},
|
||||
{"abc.edf.hjg", "a??.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab??.edf.hjg", false},
|
||||
{"abc.edf.hjg", "??.edf.hjg", false},
|
||||
}
|
||||
|
||||
for i, c := range cases {
|
||||
t.Run(c.s, func(t *testing.T) {
|
||||
result := Match(c.pattern, c.s)
|
||||
if c.result != result {
|
||||
t.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func match(pattern, name string) bool { // https://research.swtch.com/glob
|
||||
px := 0
|
||||
nx := 0
|
||||
nextPx := 0
|
||||
nextNx := 0
|
||||
for px < len(pattern) || nx < len(name) {
|
||||
if px < len(pattern) {
|
||||
c := pattern[px]
|
||||
switch c {
|
||||
default: // ordinary character
|
||||
if nx < len(name) && name[nx] == c {
|
||||
px++
|
||||
nx++
|
||||
continue
|
||||
}
|
||||
case '?': // single-character wildcard
|
||||
if nx < len(name) {
|
||||
px++
|
||||
nx++
|
||||
continue
|
||||
}
|
||||
case '*': // zero-or-more-character wildcard
|
||||
// Try to match at nx.
|
||||
// If that doesn't work out,
|
||||
// restart at nx+1 next.
|
||||
nextPx = px
|
||||
nextNx = nx + 1
|
||||
px++
|
||||
continue
|
||||
}
|
||||
}
|
||||
// Mismatch. Maybe restart.
|
||||
if 0 < nextNx && nextNx <= len(name) {
|
||||
px = nextPx
|
||||
nx = nextNx
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Matched all of pattern to all of name. Success.
|
||||
return true
|
||||
}
|
||||
|
||||
func FuzzMatch(f *testing.F) {
|
||||
f.Fuzz(func(t *testing.T, pattern, name string) {
|
||||
result1 := Match(pattern, name)
|
||||
result2 := match(pattern, name)
|
||||
if result1 != result2 {
|
||||
t.Fatalf("Match failed for pattern `%s` and name `%s`", pattern, name)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMatch(b *testing.B) {
|
||||
cases := []struct {
|
||||
s string
|
||||
pattern string
|
||||
result bool
|
||||
}{
|
||||
{"abc.edf.hjg", "abc.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab.cedf.hjg", false},
|
||||
{"abc.edf.hjg", "abc.edfh.jg", false},
|
||||
{"abc.edf.hjg", "abc.edf.hjq", false},
|
||||
|
||||
{"abc.edf.hjg", "abc.*.hjg", true},
|
||||
{"abc.edf.hjg", "abc.*.hjq", false},
|
||||
{"abc.edf.hjg", "abc*hjg", true},
|
||||
{"abc.edf.hjg", "abc*hjq", false},
|
||||
{"abc.edf.hjg", "a*g", true},
|
||||
{"abc.edf.hjg", "a*q", false},
|
||||
|
||||
{"abc.edf.hjg", "ab?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "?b?.edf.hjg", true},
|
||||
{"abc.edf.hjg", "??c.edf.hjg", true},
|
||||
{"abc.edf.hjg", "a??.edf.hjg", true},
|
||||
{"abc.edf.hjg", "ab??.edf.hjg", false},
|
||||
{"abc.edf.hjg", "??.edf.hjg", false},
|
||||
|
||||
{"r4.cdn-aa-wow-this-is-long-a1.video-yajusenpai1145141919810-oh-hell-yeah-this-is-also-very-long-and-sukka-the-fox-has-a-very-big-fluffy-fox-tail-ao-wu-ao-wu-regex-and-wildcard-both-might-have-deadly-back-tracing-issue-be-careful-or-use-linear-matching.com", "*.cdn-*-*.video**.com", true},
|
||||
}
|
||||
|
||||
b.Run("Match", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range cases {
|
||||
result := Match(c.pattern, c.s)
|
||||
if c.result != result {
|
||||
b.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("match", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, c := range cases {
|
||||
result := match(c.pattern, c.s)
|
||||
if c.result != result {
|
||||
b.Errorf("Test %d: Expected `%v`, found `%v`; With Pattern: `%s` and String: `%s`", i+1, c.result, result, c.pattern, c.s)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -157,6 +156,7 @@ type DNS struct {
|
||||
EnhancedMode C.DNSMode
|
||||
DefaultNameserver []dns.NameServer
|
||||
CacheAlgorithm string
|
||||
CacheMaxSize int
|
||||
FakeIPRange *fakeip.Pool
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
NameServerPolicy []dns.Policy
|
||||
@@ -224,6 +224,7 @@ type RawDNS struct {
|
||||
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
|
||||
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
||||
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
|
||||
CacheMaxSize int `yaml:"cache-max-size" json:"cache-max-size"`
|
||||
NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"`
|
||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
||||
DirectNameServer []string `yaml:"direct-nameserver" json:"direct-nameserver"`
|
||||
@@ -270,6 +271,7 @@ type RawTun struct {
|
||||
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||
@@ -296,6 +298,10 @@ type RawTun struct {
|
||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
|
||||
// darwin special config
|
||||
RecvMsgX bool `yaml:"recvmsgx" json:"recvmsgx,omitempty"`
|
||||
SendMsgX bool `yaml:"sendmsgx" json:"sendmsgx,omitempty"`
|
||||
}
|
||||
|
||||
type RawTuicServer struct {
|
||||
@@ -513,6 +519,8 @@ func DefaultRawConfig() *RawConfig {
|
||||
AutoRoute: true,
|
||||
AutoDetectInterface: true,
|
||||
Inet6Address: []netip.Prefix{netip.MustParsePrefix("fdfe:dcba:9876::1/126")},
|
||||
RecvMsgX: true,
|
||||
SendMsgX: false, // In the current implementation, if enabled, the kernel may freeze during multi-thread downloads, so it is disabled by default.
|
||||
},
|
||||
TuicServer: RawTuicServer{
|
||||
Enable: false,
|
||||
@@ -837,8 +845,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
AllProxies []string
|
||||
hasGlobal bool
|
||||
)
|
||||
proxiesList := list.New()
|
||||
groupsList := list.New()
|
||||
|
||||
proxies["DIRECT"] = adapter.NewProxy(outbound.NewDirect())
|
||||
proxies["REJECT"] = adapter.NewProxy(outbound.NewReject())
|
||||
@@ -860,7 +866,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
proxies[proxy.Name()] = proxy
|
||||
proxyList = append(proxyList, proxy.Name())
|
||||
AllProxies = append(AllProxies, proxy.Name())
|
||||
proxiesList.PushBack(mapping)
|
||||
}
|
||||
|
||||
// keep the original order of ProxyGroups in config file
|
||||
@@ -873,7 +878,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
hasGlobal = true
|
||||
}
|
||||
proxyList = append(proxyList, groupName)
|
||||
groupsList.PushBack(mapping)
|
||||
}
|
||||
|
||||
// check if any loop exists and sort the ProxyGroups
|
||||
@@ -1039,46 +1043,20 @@ func parseRules(rulesConfig []string, proxies map[string]C.Proxy, ruleProviders
|
||||
|
||||
// parse rules
|
||||
for idx, line := range rulesConfig {
|
||||
rule := trimArr(strings.Split(line, ","))
|
||||
var (
|
||||
payload string
|
||||
target string
|
||||
params []string
|
||||
ruleName = strings.ToUpper(rule[0])
|
||||
)
|
||||
|
||||
l := len(rule)
|
||||
|
||||
if ruleName == "NOT" || ruleName == "OR" || ruleName == "AND" || ruleName == "SUB-RULE" || ruleName == "DOMAIN-REGEX" || ruleName == "PROCESS-NAME-REGEX" || ruleName == "PROCESS-PATH-REGEX" {
|
||||
target = rule[l-1]
|
||||
payload = strings.Join(rule[1:l-1], ",")
|
||||
} else {
|
||||
if l < 2 {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line)
|
||||
}
|
||||
if l < 4 {
|
||||
rule = append(rule, make([]string, 4-l)...)
|
||||
}
|
||||
if ruleName == "MATCH" {
|
||||
l = 2
|
||||
}
|
||||
if l >= 3 {
|
||||
l = 3
|
||||
payload = rule[1]
|
||||
}
|
||||
target = rule[l-1]
|
||||
params = rule[l:]
|
||||
tp, payload, target, params := RC.ParseRulePayload(line, true)
|
||||
if target == "" {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: format invalid", format, idx, line)
|
||||
}
|
||||
|
||||
if _, ok := proxies[target]; !ok {
|
||||
if ruleName != "SUB-RULE" {
|
||||
if tp != "SUB-RULE" {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: proxy [%s] not found", format, idx, line, target)
|
||||
} else if _, ok = subRules[target]; !ok {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: sub-rule [%s] not found", format, idx, line, target)
|
||||
}
|
||||
}
|
||||
|
||||
params = trimArr(params)
|
||||
parsed, parseErr := R.ParseRule(ruleName, payload, target, params, subRules)
|
||||
parsed, parseErr := R.ParseRule(tp, payload, target, params, subRules)
|
||||
if parseErr != nil {
|
||||
return nil, fmt.Errorf("%s[%d] [%s] error: %s", format, idx, line, parseErr.Error())
|
||||
}
|
||||
@@ -1168,10 +1146,19 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
||||
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error())
|
||||
}
|
||||
|
||||
proxyName := u.Fragment
|
||||
var proxyName string
|
||||
params := map[string]string{}
|
||||
for _, s := range strings.Split(u.Fragment, "&") {
|
||||
arr := strings.SplitN(s, "=", 2)
|
||||
switch len(arr) {
|
||||
case 1:
|
||||
proxyName = arr[0]
|
||||
case 2:
|
||||
params[arr[0]] = arr[1]
|
||||
}
|
||||
}
|
||||
|
||||
var addr, dnsNetType string
|
||||
params := map[string]string{}
|
||||
switch u.Scheme {
|
||||
case "udp":
|
||||
addr, err = hostWithDefaultPort(u.Host, "53")
|
||||
@@ -1189,23 +1176,8 @@ func parseNameServer(servers []string, respectRules bool, preferH3 bool) ([]dns.
|
||||
addr, err = hostWithDefaultPort(u.Host, "80")
|
||||
}
|
||||
if err == nil {
|
||||
proxyName = ""
|
||||
clearURL := url.URL{Scheme: u.Scheme, Host: addr, Path: u.Path, User: u.User}
|
||||
addr = clearURL.String()
|
||||
if len(u.Fragment) != 0 {
|
||||
for _, s := range strings.Split(u.Fragment, "&") {
|
||||
arr := strings.Split(s, "=")
|
||||
if len(arr) == 0 {
|
||||
continue
|
||||
} else if len(arr) == 1 {
|
||||
proxyName = arr[0]
|
||||
} else if len(arr) == 2 {
|
||||
params[arr[0]] = arr[1]
|
||||
} else {
|
||||
params[arr[0]] = strings.Join(arr[1:], "=")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case "quic":
|
||||
addr, err = hostWithDefaultPort(u.Host, "853")
|
||||
@@ -1382,6 +1354,8 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
IPv6: cfg.IPv6,
|
||||
UseSystemHosts: cfg.UseSystemHosts,
|
||||
EnhancedMode: cfg.EnhancedMode,
|
||||
CacheAlgorithm: cfg.CacheAlgorithm,
|
||||
CacheMaxSize: cfg.CacheMaxSize,
|
||||
}
|
||||
var err error
|
||||
if dnsCfg.NameServer, err = parseNameServer(cfg.NameServer, cfg.RespectRules, cfg.PreferH3); err != nil {
|
||||
@@ -1515,12 +1489,6 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
||||
dnsCfg.Hosts = hosts
|
||||
}
|
||||
|
||||
if cfg.CacheAlgorithm == "" || cfg.CacheAlgorithm == "lru" {
|
||||
dnsCfg.CacheAlgorithm = "lru"
|
||||
} else {
|
||||
dnsCfg.CacheAlgorithm = "arc"
|
||||
}
|
||||
|
||||
return dnsCfg, nil
|
||||
}
|
||||
|
||||
@@ -1563,6 +1531,7 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
AutoRedirect: rawTun.AutoRedirect,
|
||||
AutoRedirectInputMark: rawTun.AutoRedirectInputMark,
|
||||
AutoRedirectOutputMark: rawTun.AutoRedirectOutputMark,
|
||||
LoopbackAddress: rawTun.LoopbackAddress,
|
||||
StrictRoute: rawTun.StrictRoute,
|
||||
RouteAddress: rawTun.RouteAddress,
|
||||
RouteAddressSet: rawTun.RouteAddressSet,
|
||||
@@ -1589,6 +1558,9 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||
Inet6RouteAddress: rawTun.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: rawTun.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: rawTun.Inet6RouteExcludeAddress,
|
||||
|
||||
RecvMsgX: rawTun.RecvMsgX,
|
||||
SendMsgX: rawTun.SendMsgX,
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -4,19 +4,13 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outboundgroup"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
)
|
||||
|
||||
func trimArr(arr []string) (r []string) {
|
||||
for _, e := range arr {
|
||||
r = append(r, strings.Trim(e, " "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Check if ProxyGroups form DAG(Directed Acyclic Graph), and sort all ProxyGroups by dependency order.
|
||||
// Meanwhile, record the original index in the config file.
|
||||
// If loop is detected, return an error with location of loop.
|
||||
@@ -150,6 +144,9 @@ func proxyGroupsDagSort(groupsConfig []map[string]any) error {
|
||||
}
|
||||
|
||||
func verifyIP6() bool {
|
||||
if skip, _ := strconv.ParseBool(os.Getenv("SKIP_SYSTEM_IPV6_CHECK")); skip {
|
||||
return true
|
||||
}
|
||||
if iAddrs, err := net.InterfaceAddrs(); err == nil {
|
||||
for _, addr := range iAddrs {
|
||||
if prefix, err := netip.ParsePrefix(addr.String()); err == nil {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
@@ -22,44 +21,6 @@ const (
|
||||
|
||||
type DNSMode int
|
||||
|
||||
// UnmarshalYAML unserialize EnhancedMode with yaml
|
||||
func (e *DNSMode) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML serialize EnhancedMode with yaml
|
||||
func (e DNSMode) MarshalYAML() (any, error) {
|
||||
return e.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unserialize EnhancedMode with json
|
||||
func (e *DNSMode) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
if err := json.Unmarshal(data, &tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := DNSModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialize EnhancedMode with json
|
||||
func (e DNSMode) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.String())
|
||||
}
|
||||
|
||||
// UnmarshalText unserialize EnhancedMode
|
||||
func (e *DNSMode) UnmarshalText(data []byte) error {
|
||||
mode, exist := DNSModeMapping[strings.ToLower(string(data))]
|
||||
@@ -157,40 +118,6 @@ func (e FilterMode) String() string {
|
||||
}
|
||||
}
|
||||
|
||||
func (e FilterMode) MarshalYAML() (interface{}, error) {
|
||||
return e.String(), nil
|
||||
}
|
||||
|
||||
func (e *FilterMode) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := FilterModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e FilterMode) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.String())
|
||||
}
|
||||
|
||||
func (e *FilterMode) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
if err := json.Unmarshal(data, &tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := FilterModeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid mode")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e FilterMode) MarshalText() ([]byte, error) {
|
||||
return []byte(e.String()), nil
|
||||
}
|
||||
|
||||
24
constant/features/goflags.go
Normal file
24
constant/features/goflags.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package features
|
||||
|
||||
import "runtime/debug"
|
||||
|
||||
var (
|
||||
GOARM string
|
||||
GOMIPS string
|
||||
GOAMD64 string
|
||||
)
|
||||
|
||||
func init() {
|
||||
if info, ok := debug.ReadBuildInfo(); ok {
|
||||
for _, bs := range info.Settings {
|
||||
switch bs.Key {
|
||||
case "GOARM":
|
||||
GOARM = bs.Value
|
||||
case "GOMIPS":
|
||||
GOMIPS = bs.Value
|
||||
case "GOAMD64":
|
||||
GOAMD64 = bs.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,7 @@ type RuleProvider interface {
|
||||
Provider
|
||||
Behavior() RuleBehavior
|
||||
Count() int
|
||||
Match(*constant.Metadata) bool
|
||||
ShouldResolveIP() bool
|
||||
ShouldFindProcess() bool
|
||||
Match(metadata *constant.Metadata, helper constant.RuleMatchHelper) bool
|
||||
Strategy() any
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ const (
|
||||
DomainSuffix
|
||||
DomainKeyword
|
||||
DomainRegex
|
||||
DomainWildcard
|
||||
GEOSITE
|
||||
GEOIP
|
||||
SrcGEOIP
|
||||
@@ -48,6 +49,8 @@ func (rt RuleType) String() string {
|
||||
return "DomainKeyword"
|
||||
case DomainRegex:
|
||||
return "DomainRegex"
|
||||
case DomainWildcard:
|
||||
return "DomainWildcard"
|
||||
case GEOSITE:
|
||||
return "GeoSite"
|
||||
case GEOIP:
|
||||
@@ -111,14 +114,17 @@ func (rt RuleType) String() string {
|
||||
|
||||
type Rule interface {
|
||||
RuleType() RuleType
|
||||
Match(metadata *Metadata) (bool, string)
|
||||
Match(metadata *Metadata, helper RuleMatchHelper) (bool, string)
|
||||
Adapter() string
|
||||
Payload() string
|
||||
ShouldResolveIP() bool
|
||||
ShouldFindProcess() bool
|
||||
ProviderNames() []string
|
||||
}
|
||||
|
||||
type RuleMatchHelper struct {
|
||||
ResolveIP func()
|
||||
FindProcess func()
|
||||
}
|
||||
|
||||
type RuleGroup interface {
|
||||
Rule
|
||||
GetRecodeSize() int
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package constant
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
@@ -20,42 +19,6 @@ const (
|
||||
|
||||
type TUNStack int
|
||||
|
||||
// UnmarshalYAML unserialize TUNStack with yaml
|
||||
func (e *TUNStack) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
if err := unmarshal(&tp); err != nil {
|
||||
return err
|
||||
}
|
||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid tun stack")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML serialize TUNStack with yaml
|
||||
func (e TUNStack) MarshalYAML() (any, error) {
|
||||
return e.String(), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unserialize TUNStack with json
|
||||
func (e *TUNStack) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
mode, exist := StackTypeMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid tun stack")
|
||||
}
|
||||
*e = mode
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialize TUNStack with json
|
||||
func (e TUNStack) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(e.String())
|
||||
}
|
||||
|
||||
// UnmarshalText unserialize TUNStack
|
||||
func (e *TUNStack) UnmarshalText(data []byte) error {
|
||||
mode, exist := StackTypeMapping[strings.ToLower(string(data))]
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
D "github.com/miekg/dns"
|
||||
@@ -105,3 +107,24 @@ func (c *client) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error)
|
||||
}
|
||||
|
||||
func (c *client) ResetConnection() {}
|
||||
|
||||
func newClient(addr string, resolver *Resolver, netType string, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *client {
|
||||
host, port, _ := net.SplitHostPort(addr)
|
||||
c := &client{
|
||||
Client: &D.Client{
|
||||
Net: netType,
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: host,
|
||||
},
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
port: port,
|
||||
host: host,
|
||||
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||
}
|
||||
if params["skip-cert-verify"] == "true" {
|
||||
c.TLSConfig.InsecureSkipVerify = true
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
45
dns/doh.go
45
dns/doh.go
@@ -9,7 +9,6 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -71,8 +70,6 @@ type dnsOverHTTPS struct {
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
skipCertVerify bool
|
||||
ecsPrefix netip.Prefix
|
||||
ecsOverride bool
|
||||
}
|
||||
|
||||
// type check
|
||||
@@ -105,28 +102,6 @@ func newDoHClient(urlString string, r *Resolver, preferH3 bool, params map[strin
|
||||
doh.skipCertVerify = true
|
||||
}
|
||||
|
||||
if ecs := params["ecs"]; ecs != "" {
|
||||
prefix, err := netip.ParsePrefix(ecs)
|
||||
if err != nil {
|
||||
addr, err := netip.ParseAddr(ecs)
|
||||
if err != nil {
|
||||
log.Warnln("DOH [%s] config with invalid ecs: %s", doh.addr, ecs)
|
||||
} else {
|
||||
doh.ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
|
||||
}
|
||||
} else {
|
||||
doh.ecsPrefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
if doh.ecsPrefix.IsValid() {
|
||||
log.Debugln("DOH [%s] config with ecs: %s", doh.addr, doh.ecsPrefix)
|
||||
}
|
||||
|
||||
if params["ecs-override"] == "true" {
|
||||
doh.ecsOverride = true
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
|
||||
|
||||
return doh
|
||||
@@ -154,10 +129,6 @@ func (doh *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.
|
||||
}
|
||||
}()
|
||||
|
||||
if doh.ecsPrefix.IsValid() {
|
||||
setEdns0Subnet(m, doh.ecsPrefix, doh.ecsOverride)
|
||||
}
|
||||
|
||||
// Check if there was already an active client before sending the request.
|
||||
// We'll only attempt to re-connect if there was one.
|
||||
client, isCached, err := doh.getClient(ctx)
|
||||
@@ -476,12 +447,12 @@ func (doh *dnsOverHTTPS) createTransport(ctx context.Context) (t http.RoundTripp
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// http3Transport is a wrapper over *http3.RoundTripper that tries to optimize
|
||||
// http3Transport is a wrapper over *http3.Transport that tries to optimize
|
||||
// its behavior. The main thing that it does is trying to force use a single
|
||||
// connection to a host instead of creating a new one all the time. It also
|
||||
// helps mitigate race issues with quic-go.
|
||||
type http3Transport struct {
|
||||
baseTransport *http3.RoundTripper
|
||||
baseTransport *http3.Transport
|
||||
|
||||
closed bool
|
||||
mu sync.RWMutex
|
||||
@@ -534,7 +505,7 @@ func (h *http3Transport) CloseIdleConnections() {
|
||||
// We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or
|
||||
// if it is too slow. In order to do that, this method will run two probes
|
||||
// in parallel (one for TLS, the other one for QUIC) and if QUIC is faster it
|
||||
// will create the *http3.RoundTripper instance.
|
||||
// will create the *http3.Transport instance.
|
||||
func (doh *dnsOverHTTPS) createTransportH3(
|
||||
ctx context.Context,
|
||||
tlsConfig *tls.Config,
|
||||
@@ -548,16 +519,16 @@ func (doh *dnsOverHTTPS) createTransportH3(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rt := &http3.RoundTripper{
|
||||
rt := &http3.Transport{
|
||||
Dial: func(
|
||||
ctx context.Context,
|
||||
|
||||
// Ignore the address and always connect to the one that we got
|
||||
// from the bootstrapper.
|
||||
// Ignore the address and always connect to the one that we got
|
||||
// from the bootstrapper.
|
||||
_ string,
|
||||
tlsCfg *tlsC.Config,
|
||||
cfg *quic.Config,
|
||||
) (c quic.EarlyConnection, err error) {
|
||||
) (c *quic.Conn, err error) {
|
||||
return doh.dialQuic(ctx, addr, tlsCfg, cfg)
|
||||
},
|
||||
DisableCompression: true,
|
||||
@@ -568,7 +539,7 @@ func (doh *dnsOverHTTPS) createTransportH3(
|
||||
return &http3Transport{baseTransport: rt}, nil
|
||||
}
|
||||
|
||||
func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tlsC.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
|
||||
func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tlsC.Config, cfg *quic.Config) (*quic.Conn, error) {
|
||||
ip, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
33
dns/doq.go
33
dns/doq.go
@@ -53,7 +53,7 @@ type dnsOverQUIC struct {
|
||||
|
||||
// conn is the current active QUIC connection. It can be closed and
|
||||
// re-opened when needed.
|
||||
conn quic.Connection
|
||||
conn *quic.Conn
|
||||
connMu sync.RWMutex
|
||||
|
||||
// bytesPool is a *sync.Pool we use to store byte buffers in. These byte
|
||||
@@ -61,15 +61,16 @@ type dnsOverQUIC struct {
|
||||
bytesPool *sync.Pool
|
||||
bytesPoolGuard sync.Mutex
|
||||
|
||||
addr string
|
||||
dialer *dnsDialer
|
||||
addr string
|
||||
dialer *dnsDialer
|
||||
skipCertVerify bool
|
||||
}
|
||||
|
||||
// type check
|
||||
var _ dnsClient = (*dnsOverQUIC)(nil)
|
||||
|
||||
// newDoQ returns the DNS-over-QUIC Upstream.
|
||||
func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyName string) (dnsClient, error) {
|
||||
func newDoQ(addr string, resolver *Resolver, params map[string]string, proxyAdapter C.ProxyAdapter, proxyName string) *dnsOverQUIC {
|
||||
doq := &dnsOverQUIC{
|
||||
addr: addr,
|
||||
dialer: newDNSDialer(resolver, proxyAdapter, proxyName),
|
||||
@@ -79,8 +80,12 @@ func newDoQ(resolver *Resolver, addr string, proxyAdapter C.ProxyAdapter, proxyN
|
||||
},
|
||||
}
|
||||
|
||||
if params["skip-cert-verify"] == "true" {
|
||||
doq.skipCertVerify = true
|
||||
}
|
||||
|
||||
runtime.SetFinalizer(doq, (*dnsOverQUIC).Close)
|
||||
return doq, nil
|
||||
return doq
|
||||
}
|
||||
|
||||
// Address implements the Upstream interface for *dnsOverQUIC.
|
||||
@@ -152,7 +157,7 @@ func (doq *dnsOverQUIC) ResetConnection() {
|
||||
// exchangeQUIC attempts to open a QUIC connection, send the DNS message
|
||||
// through it and return the response it got from the server.
|
||||
func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.Msg, err error) {
|
||||
var conn quic.Connection
|
||||
var conn *quic.Conn
|
||||
conn, err = doq.getConnection(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,7 +169,7 @@ func (doq *dnsOverQUIC) exchangeQUIC(ctx context.Context, msg *D.Msg) (resp *D.M
|
||||
return nil, fmt.Errorf("failed to pack DNS message for DoQ: %w", err)
|
||||
}
|
||||
|
||||
var stream quic.Stream
|
||||
var stream *quic.Stream
|
||||
stream, err = doq.openStream(ctx, conn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -217,12 +222,12 @@ func (doq *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
|
||||
return doq.bytesPool
|
||||
}
|
||||
|
||||
// getConnection opens or returns an existing quic.Connection. useCached
|
||||
// getConnection opens or returns an existing *quic.Conn. useCached
|
||||
// argument controls whether we should try to use the existing cached
|
||||
// connection. If it is false, we will forcibly create a new connection and
|
||||
// close the existing one if needed.
|
||||
func (doq *dnsOverQUIC) getConnection(ctx context.Context, useCached bool) (quic.Connection, error) {
|
||||
var conn quic.Connection
|
||||
func (doq *dnsOverQUIC) getConnection(ctx context.Context, useCached bool) (*quic.Conn, error) {
|
||||
var conn *quic.Conn
|
||||
doq.connMu.RLock()
|
||||
conn = doq.conn
|
||||
if conn != nil && useCached {
|
||||
@@ -277,7 +282,7 @@ func (doq *dnsOverQUIC) resetQUICConfig() {
|
||||
}
|
||||
|
||||
// openStream opens a new QUIC stream for the specified connection.
|
||||
func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (quic.Stream, error) {
|
||||
func (doq *dnsOverQUIC) openStream(ctx context.Context, conn *quic.Conn) (*quic.Stream, error) {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
@@ -297,7 +302,7 @@ func (doq *dnsOverQUIC) openStream(ctx context.Context, conn quic.Connection) (q
|
||||
}
|
||||
|
||||
// openConnection opens a new QUIC connection.
|
||||
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connection, err error) {
|
||||
func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn *quic.Conn, err error) {
|
||||
// we're using bootstrapped address instead of what's passed to the function
|
||||
// it does not create an actual connection, but it helps us determine
|
||||
// what IP is actually reachable (when there're v4/v6 addresses).
|
||||
@@ -329,7 +334,7 @@ func (doq *dnsOverQUIC) openConnection(ctx context.Context) (conn quic.Connectio
|
||||
tlsConfig := ca.GetGlobalTLSConfig(
|
||||
&tls.Config{
|
||||
ServerName: host,
|
||||
InsecureSkipVerify: false,
|
||||
InsecureSkipVerify: doq.skipCertVerify,
|
||||
NextProtos: []string{
|
||||
NextProtoDQ,
|
||||
},
|
||||
@@ -377,7 +382,7 @@ func (doq *dnsOverQUIC) closeConnWithError(err error) {
|
||||
}
|
||||
|
||||
// readMsg reads the incoming DNS message from the QUIC stream.
|
||||
func (doq *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
|
||||
func (doq *dnsOverQUIC) readMsg(stream *quic.Stream) (m *D.Msg, err error) {
|
||||
pool := doq.getBytesPool()
|
||||
bufPtr := pool.Get().(*[]byte)
|
||||
|
||||
|
||||
@@ -81,8 +81,8 @@ func (h *ResolverEnhancer) InsertHostByIP(ip netip.Addr, host string) {
|
||||
}
|
||||
|
||||
func (h *ResolverEnhancer) FlushFakeIP() error {
|
||||
if h.fakePool != nil {
|
||||
return h.fakePool.FlushFakeIP()
|
||||
if pool := h.fakePool; pool != nil {
|
||||
return pool.FlushFakeIP()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,12 +9,7 @@ import (
|
||||
var systemResolver []dnsClient
|
||||
|
||||
func FlushCacheWithDefaultResolver() {
|
||||
if r := resolver.DefaultResolver; r != nil {
|
||||
r.ClearCache()
|
||||
}
|
||||
if r := resolver.SystemResolver; r != nil {
|
||||
r.ClearCache()
|
||||
}
|
||||
resolver.ClearCache()
|
||||
resolver.ResetConnection()
|
||||
}
|
||||
|
||||
|
||||
@@ -459,13 +459,18 @@ type Config struct {
|
||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||
Policy []Policy
|
||||
CacheAlgorithm string
|
||||
CacheMaxSize int
|
||||
}
|
||||
|
||||
func (config Config) newCache() dnsCache {
|
||||
if config.CacheAlgorithm == "" || config.CacheAlgorithm == "lru" {
|
||||
return lru.New(lru.WithSize[string, *D.Msg](4096), lru.WithStale[string, *D.Msg](true))
|
||||
} else {
|
||||
return arc.New(arc.WithSize[string, *D.Msg](4096))
|
||||
if config.CacheMaxSize == 0 {
|
||||
config.CacheMaxSize = 4096
|
||||
}
|
||||
switch config.CacheAlgorithm {
|
||||
case "arc":
|
||||
return arc.New(arc.WithSize[string, *D.Msg](config.CacheMaxSize))
|
||||
default:
|
||||
return lru.New(lru.WithSize[string, *D.Msg](config.CacheMaxSize), lru.WithStale[string, *D.Msg](true))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
125
dns/util.go
125
dns/util.go
@@ -2,10 +2,8 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -92,46 +90,95 @@ func isIPRequest(q D.Question) bool {
|
||||
func transform(servers []NameServer, resolver *Resolver) []dnsClient {
|
||||
ret := make([]dnsClient, 0, len(servers))
|
||||
for _, s := range servers {
|
||||
var c dnsClient
|
||||
switch s.Net {
|
||||
case "https":
|
||||
ret = append(ret, newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName))
|
||||
continue
|
||||
c = newDoHClient(s.Addr, resolver, s.PreferH3, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||
case "dhcp":
|
||||
ret = append(ret, newDHCPClient(s.Addr))
|
||||
continue
|
||||
c = newDHCPClient(s.Addr)
|
||||
case "system":
|
||||
ret = append(ret, newSystemClient())
|
||||
continue
|
||||
c = newSystemClient()
|
||||
case "rcode":
|
||||
ret = append(ret, newRCodeClient(s.Addr))
|
||||
continue
|
||||
c = newRCodeClient(s.Addr)
|
||||
case "quic":
|
||||
if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter, s.ProxyName); err == nil {
|
||||
ret = append(ret, doq)
|
||||
} else {
|
||||
log.Fatalln("DoQ format error: %v", err)
|
||||
}
|
||||
continue
|
||||
c = newDoQ(s.Addr, resolver, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||
default:
|
||||
c = newClient(s.Addr, resolver, s.Net, s.Params, s.ProxyAdapter, s.ProxyName)
|
||||
}
|
||||
|
||||
host, port, _ := net.SplitHostPort(s.Addr)
|
||||
ret = append(ret, &client{
|
||||
Client: &D.Client{
|
||||
Net: s.Net,
|
||||
TLSConfig: &tls.Config{
|
||||
ServerName: host,
|
||||
},
|
||||
UDPSize: 4096,
|
||||
Timeout: 5 * time.Second,
|
||||
},
|
||||
port: port,
|
||||
host: host,
|
||||
dialer: newDNSDialer(resolver, s.ProxyAdapter, s.ProxyName),
|
||||
})
|
||||
c = warpClientWithEdns0Subnet(c, s.Params)
|
||||
|
||||
if s.Params["disable-ipv4"] == "true" {
|
||||
c = warpClientWithDisableType(c, D.TypeA)
|
||||
}
|
||||
|
||||
if s.Params["disable-ipv6"] == "true" {
|
||||
c = warpClientWithDisableType(c, D.TypeAAAA)
|
||||
}
|
||||
|
||||
ret = append(ret, c)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
type clientWithDisableType struct {
|
||||
dnsClient
|
||||
qType uint16
|
||||
}
|
||||
|
||||
func (c clientWithDisableType) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||
if len(m.Question) > 0 {
|
||||
q := m.Question[0]
|
||||
if q.Qtype == c.qType {
|
||||
return handleMsgWithEmptyAnswer(m), nil
|
||||
}
|
||||
}
|
||||
return c.dnsClient.ExchangeContext(ctx, m)
|
||||
}
|
||||
|
||||
func warpClientWithDisableType(c dnsClient, qType uint16) dnsClient {
|
||||
return clientWithDisableType{c, qType}
|
||||
}
|
||||
|
||||
type clientWithEdns0Subnet struct {
|
||||
dnsClient
|
||||
ecsPrefix netip.Prefix
|
||||
ecsOverride bool
|
||||
}
|
||||
|
||||
func (c clientWithEdns0Subnet) ExchangeContext(ctx context.Context, m *D.Msg) (*D.Msg, error) {
|
||||
m = m.Copy()
|
||||
setEdns0Subnet(m, c.ecsPrefix, c.ecsOverride)
|
||||
return c.dnsClient.ExchangeContext(ctx, m)
|
||||
}
|
||||
|
||||
func warpClientWithEdns0Subnet(c dnsClient, params map[string]string) dnsClient {
|
||||
var ecsPrefix netip.Prefix
|
||||
var ecsOverride bool
|
||||
if ecs := params["ecs"]; ecs != "" {
|
||||
prefix, err := netip.ParsePrefix(ecs)
|
||||
if err != nil {
|
||||
addr, err := netip.ParseAddr(ecs)
|
||||
if err != nil {
|
||||
log.Warnln("DNS [%s] config with invalid ecs: %s", c.Address(), ecs)
|
||||
} else {
|
||||
ecsPrefix = netip.PrefixFrom(addr, addr.BitLen())
|
||||
}
|
||||
} else {
|
||||
ecsPrefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
if ecsPrefix.IsValid() {
|
||||
log.Debugln("DNS [%s] config with ecs: %s", c.Address(), ecsPrefix)
|
||||
if params["ecs-override"] == "true" {
|
||||
ecsOverride = true
|
||||
}
|
||||
return clientWithEdns0Subnet{c, ecsPrefix, ecsOverride}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
||||
msg := &D.Msg{}
|
||||
msg.Answer = []D.RR{}
|
||||
@@ -184,8 +231,7 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
fast, ctx := picker.WithTimeout[*D.Msg](ctx, resolver.DefaultDNSTimeout)
|
||||
defer fast.Close()
|
||||
domain := msgToDomain(m)
|
||||
qType, qTypeStr := msgToQtype(m)
|
||||
var noIpMsg *D.Msg
|
||||
_, qTypeStr := msgToQtype(m)
|
||||
for _, client := range clients {
|
||||
if _, isRCodeClient := client.(rcodeClient); isRCodeClient {
|
||||
msg, err = client.ExchangeContext(ctx, m)
|
||||
@@ -204,27 +250,12 @@ func batchExchange(ctx context.Context, clients []dnsClient, m *D.Msg) (msg *D.M
|
||||
}
|
||||
ips := msgToIP(m)
|
||||
log.Debugln("[DNS] %s --> %s %s from %s", domain, ips, qTypeStr, client.Address())
|
||||
switch qType {
|
||||
case D.TypeAAAA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
case D.TypeA:
|
||||
if len(ips) == 0 {
|
||||
noIpMsg = m
|
||||
return nil, resolver.ErrIPNotFound
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
})
|
||||
}
|
||||
|
||||
msg = fast.Wait()
|
||||
if msg == nil {
|
||||
if noIpMsg != nil {
|
||||
return noIpMsg, false, nil
|
||||
}
|
||||
err = errors.New("all DNS requests failed")
|
||||
if fErr := fast.Error(); fErr != nil {
|
||||
err = fmt.Errorf("%w, first error: %w", err, fErr)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
os="mihomo-linux-"
|
||||
case $TARGETPLATFORM in
|
||||
"linux/amd64")
|
||||
arch="amd64-compatible"
|
||||
arch="amd64-v1"
|
||||
;;
|
||||
"linux/386")
|
||||
arch="386"
|
||||
|
||||
@@ -632,6 +632,21 @@ proxies: # socks5
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
|
||||
- name: "vless-encryption"
|
||||
type: vless
|
||||
server: server
|
||||
port: 443
|
||||
uuid: uuid
|
||||
network: tcp
|
||||
# -------------------------
|
||||
# vless encryption客户端配置:
|
||||
# (native/xorpub 的 XTLS 可以 Splice。只使用 1-RTT 模式 / 若服务端发的 ticket 中秒数不为零则 0-RTT 复用)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
# -------------------------
|
||||
encryption: "mlkem768x25519plus.native/xorpub/random.1rtt/0rtt.(X25519 Password).(ML-KEM-768 Client)..."
|
||||
tls: false #可以不开启tls
|
||||
udp: true
|
||||
|
||||
- name: "vless-reality-vision"
|
||||
type: vless
|
||||
server: server
|
||||
@@ -846,6 +861,16 @@ proxies: # socks5
|
||||
# h2: 67543
|
||||
# h4: 32345
|
||||
# h3: 123123
|
||||
# # AmneziaWG v1.5
|
||||
# i1: <b 0xf6ab3267fa><c><b 0xf6ab><t><r 10><wt 10>
|
||||
# i2: <b 0xf6ab3267fa><r 100>
|
||||
# i3: ""
|
||||
# i4: ""
|
||||
# i5: ""
|
||||
# j1: <b 0xffffffff><c><b 0xf6ab><t><r 10>
|
||||
# j2: <c><b 0xf6ab><t><wt 1000>
|
||||
# j3: <t><b 0xf6ab><c><r 10>
|
||||
# itime: 60
|
||||
|
||||
# tuic
|
||||
- name: tuic
|
||||
@@ -922,6 +947,8 @@ proxies: # socks5
|
||||
password: password
|
||||
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
|
||||
# multiplexing: MULTIPLEXING_LOW
|
||||
# 如果想开启 0-RTT 握手,请设置为 HANDSHAKE_NO_WAIT,否则请设置为 HANDSHAKE_STANDARD。默认值为 HANDSHAKE_STANDARD
|
||||
# handshake-mode: HANDSHAKE_STANDARD
|
||||
|
||||
# anytls
|
||||
- name: anytls
|
||||
@@ -1020,7 +1047,7 @@ proxy-providers:
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 proxies 文件夹,文件名为 url 的 md5
|
||||
url: "url"
|
||||
interval: 3600
|
||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则(即Windows下以分号分割,其他系统下以冒号分割)
|
||||
proxy: DIRECT
|
||||
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||
header:
|
||||
@@ -1077,7 +1104,7 @@ rule-providers:
|
||||
rule1:
|
||||
behavior: classical # domain ipcidr
|
||||
interval: 259200
|
||||
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||
path: /path/to/save/file.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到其他位置,请通过设置 SAFE_PATHS 环境变量指定额外的安全路径。该环境变量的语法同本操作系统的PATH环境变量解析规则(即Windows下以分号分割,其他系统下以冒号分割)
|
||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
||||
url: "url"
|
||||
proxy: DIRECT
|
||||
@@ -1119,6 +1146,7 @@ rules:
|
||||
- DOMAIN-REGEX,^abc,DIRECT
|
||||
- DOMAIN-SUFFIX,baidu.com,DIRECT
|
||||
- DOMAIN-KEYWORD,google,ss1
|
||||
- DOMAIN-WILDCARD,test.*.mihomo.com,ss1
|
||||
- IP-CIDR,1.1.1.1/32,ss1
|
||||
- IP-CIDR6,2409::/64,DIRECT
|
||||
# 当满足条件是 TCP 或 UDP 流量时,使用名为 sub-rule-name1 的规则集
|
||||
@@ -1276,6 +1304,16 @@ listeners:
|
||||
# - 0123456789abcdef
|
||||
# server-names:
|
||||
# - test.com
|
||||
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||
# limit-fallback-upload:
|
||||
# after-bytes: 0 # 传输指定字节后开始限速
|
||||
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
# limit-fallback-download:
|
||||
# after-bytes: 0 # 传输指定字节后开始限速
|
||||
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
|
||||
- name: tuic-in-1
|
||||
type: tuic
|
||||
@@ -1325,6 +1363,12 @@ listeners:
|
||||
flow: xtls-rprx-vision
|
||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||
# -------------------------
|
||||
# vless encryption服务端配置:
|
||||
# (原生外观 / 只 XOR 公钥 / 全随机数。只允许 1-RTT 模式 / 同时允许 1-RTT 模式与 600 秒复用的 0-RTT 模式)
|
||||
# / 是只能选一个,后面 base64 至少一个,无限串联,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
|
||||
# -------------------------
|
||||
# decryption: "mlkem768x25519plus.native/xorpub/random.1rtt/600s.(X25519 PrivateKey).(ML-KEM-768 Seed)..."
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
@@ -1343,7 +1387,17 @@ listeners:
|
||||
- 0123456789abcdef
|
||||
server-names:
|
||||
- test.com
|
||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
||||
#下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||
#回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||
limit-fallback-upload:
|
||||
after-bytes: 0 # 传输指定字节后开始限速
|
||||
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
limit-fallback-download:
|
||||
after-bytes: 0 # 传输指定字节后开始限速
|
||||
bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “decryption” 的其中一项 ###
|
||||
|
||||
- name: anytls-in-1
|
||||
type: anytls
|
||||
@@ -1393,6 +1447,16 @@ listeners:
|
||||
# - 0123456789abcdef
|
||||
# server-names:
|
||||
# - test.com
|
||||
# #下列两个 limit 为选填,可对未通过验证的回落连接限速,bytesPerSec 默认为 0 即不启用
|
||||
# #回落限速是一种特征,不建议启用,如果您是面板/一键脚本开发者,务必让这些参数随机化
|
||||
# limit-fallback-upload:
|
||||
# after-bytes: 0 # 传输指定字节后开始限速
|
||||
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
# limit-fallback-download:
|
||||
# after-bytes: 0 # 传输指定字节后开始限速
|
||||
# bytes-per-sec: 0 # 基准速率(字节/秒)
|
||||
# burst-bytes-per-sec: 0 # 突发速率(字节/秒),大于 bytesPerSec 时生效
|
||||
# ss-option: # like trojan-go's `shadowsocks` config
|
||||
# enabled: false
|
||||
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||
@@ -1431,6 +1495,7 @@ listeners:
|
||||
# masquerade: http://127.0.0.1:8080 #作为反向代理
|
||||
# masquerade: https://127.0.0.1:8080 #作为反向代理
|
||||
|
||||
# 注意,listeners中的tun仅提供给高级用户使用,普通用户应使用顶层配置中的tun
|
||||
- name: tun-in-1
|
||||
type: tun
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
|
||||
48
go.mod
48
go.mod
@@ -3,52 +3,50 @@ module github.com/metacubex/mihomo
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/3andne/restls-client-go v0.1.6
|
||||
github.com/bahlo/generic-list-go v0.2.0
|
||||
github.com/coreos/go-iptables v0.8.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/enfein/mieru/v3 v3.13.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/enfein/mieru/v3 v3.19.1
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.3.2
|
||||
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 // lastest version compatible with golang1.20
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a
|
||||
github.com/metacubex/bart v0.20.5
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
||||
github.com/metacubex/chacha v0.1.2
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b
|
||||
github.com/metacubex/blake3 v0.1.0
|
||||
github.com/metacubex/chacha v0.1.5
|
||||
github.com/metacubex/fswatch v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/sing v0.5.3
|
||||
github.com/metacubex/sing-mux v0.3.2
|
||||
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4
|
||||
github.com/metacubex/restls-client-go v0.1.7
|
||||
github.com/metacubex/sing v0.5.5
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c
|
||||
github.com/metacubex/sing-vmess v0.2.2
|
||||
github.com/metacubex/sing-tun v0.4.7
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4
|
||||
github.com/metacubex/utls v1.7.3
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f
|
||||
github.com/miekg/dns v1.1.63 // lastest version compatible with golang1.20
|
||||
github.com/mroth/weightedrand/v2 v2.1.0
|
||||
github.com/openacid/low v0.1.21
|
||||
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1
|
||||
github.com/sagernet/cors v1.2.1
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||
github.com/samber/lo v1.50.0
|
||||
github.com/samber/lo v1.51.0
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/stretchr/testify v1.11.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||
@@ -61,7 +59,6 @@ require (
|
||||
golang.org/x/sys v0.30.0 // lastest version compatible with golang1.20
|
||||
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0 // lastest version compatible with golang1.20
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -70,9 +67,8 @@ require (
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/andybalholm/brotli v1.0.6 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.3.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.3 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 // indirect
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||
@@ -88,9 +84,11 @@ require (
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // indirect
|
||||
github.com/metacubex/ascon v0.1.0 // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // indirect
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 // indirect
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||
|
||||
100
go.sum
100
go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/3andne/restls-client-go v0.1.6 h1:tRx/YilqW7iHpgmEL4E1D8dAsuB0tFF3uvncS+B6I08=
|
||||
github.com/3andne/restls-client-go v0.1.6/go.mod h1:iEdTZNt9kzPIxjIGSMScUFSBrUH6bFRNg0BWlP4orEY=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0 h1:smavTzSMAx8iuVlGb4pEwl9MD2qicqMzuXR2QWp2/Pg=
|
||||
github.com/RyuaNerin/go-krypto v1.3.0/go.mod h1:9R9TU936laAIqAmjcHo/LsaXYOZlymudOAxjaBf62UM=
|
||||
github.com/RyuaNerin/testingutil v0.1.0 h1:IYT6JL57RV3U2ml3dLHZsVtPOP6yNK7WUVdzzlpNrss=
|
||||
@@ -17,19 +15,18 @@ github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx2
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.19.1 h1:19b9kgFC7oJXX9RLEO5Pi1gO6yek5cWlpK7IJVUoE8I=
|
||||
github.com/enfein/mieru/v3 v3.19.1/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358 h1:kXYqH/sL8dS/FdoFjr12ePjnLPorPo2FsnrHNuXSDyo=
|
||||
github.com/ericlagergren/aegis v0.0.0-20250325060835-cd0defd64358/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -43,8 +40,8 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
@@ -82,27 +79,29 @@ github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtL
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a h1:c1QSGpacSeQdBdWcEKZKGuWLcqIG2wxHEygAcXuDwS4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20250820070344-732c0c9d418a/go.mod h1:MsM/5czONyXMJ3PRr5DbQ4O/BxzAnJWOIcJdLzW6qHY=
|
||||
github.com/metacubex/ascon v0.1.0 h1:6ZWxmXYszT1XXtwkf6nxfFhc/OTtQ9R3Vyj1jN32lGM=
|
||||
github.com/metacubex/ascon v0.1.0/go.mod h1:eV5oim4cVPPdEL8/EYaTZ0iIKARH9pnhAK/fcT5Kacc=
|
||||
github.com/metacubex/bart v0.20.5 h1:XkgLZ17QxfxkqKdGsojoM2Zu01mmHyyQSFzt2/calTM=
|
||||
github.com/metacubex/bart v0.20.5/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b h1:j7dadXD8I2KTmMt8jg1JcaP1ANL3JEObJPdANKcSYPY=
|
||||
github.com/metacubex/bbolt v0.0.0-20250725135710-010dbbbb7a5b/go.mod h1:+WmP0VJZDkDszvpa83HzfUp6QzARl/IKkMorH4+nODw=
|
||||
github.com/metacubex/blake3 v0.1.0 h1:KGnjh/56REO7U+cgZA8dnBhxdP7jByrG7hTP+bu6cqY=
|
||||
github.com/metacubex/blake3 v0.1.0/go.mod h1:CCkLdzFrqf7xmxCdhQFvJsRRV2mwOLDoSPg6vUTB9Uk=
|
||||
github.com/metacubex/chacha v0.1.5 h1:fKWMb/5c7ZrY8Uoqi79PPFxl+qwR7X/q0OrsAubyX2M=
|
||||
github.com/metacubex/chacha v0.1.5/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/fswatch v0.1.1 h1:jqU7C/v+g0qc2RUFgmAOPoVvfl2BXXUXEumn6oQuxhU=
|
||||
github.com/metacubex/fswatch v0.1.1/go.mod h1:czrTT7Zlbz7vWft8RQu9Qqh+JoX+Nnb+UabuyN1YsgI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
@@ -111,42 +110,43 @@ github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793 h1:1Qpuy+sU3DmyX9HwI+CrBT/oLNJngvBorR2RbajJcqo=
|
||||
github.com/metacubex/nftables v0.0.0-20250503052935-30a69ab87793/go.mod h1:RjRNb4G52yAgfR+Oe/kp9G4PJJ97Fnj89eY1BFO3YyA=
|
||||
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639 h1:L+1brQNzBhCCxWlicwfK1TlceemCRmrDE4HmcVHc29w=
|
||||
github.com/metacubex/quic-go v0.52.1-0.20250522021943-aef454b9e639/go.mod h1:Kc6h++Q/zf3AxcUCevJhJwgrskJumv+pZdR8g/E/10k=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295 h1:8JVlYuE8uSJAvmyCd4TjvDxs57xjb0WxEoaWafK5+qs=
|
||||
github.com/metacubex/quic-go v0.54.1-0.20250730114134-a1ae705fe295/go.mod h1:1lktQFtCD17FZliVypbrDHwbsFSsmz2xz2TRXydvB5c=
|
||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||
github.com/metacubex/restls-client-go v0.1.7 h1:eCwiXCTQb5WJu9IlgYvDBA1OgrINv58dEe7hcN5H15k=
|
||||
github.com/metacubex/restls-client-go v0.1.7/go.mod h1:BN/U52vPw7j8VTSh2vleD/MnmVKCov84mS5VcjVHH4g=
|
||||
github.com/metacubex/sing v0.5.2/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing v0.5.3 h1:QWdN16WFKMk06x4nzkc8SvZ7y2x+TLQrpkPoHs+WSVM=
|
||||
github.com/metacubex/sing v0.5.3/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.2 h1:nJv52pyRivHcaZJKk2JgxpaVvj1GAXG81scSa9N7ncw=
|
||||
github.com/metacubex/sing-mux v0.3.2/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f h1:mP3vIm+9hRFI0C0Vl3pE0NESF/L85FDbuB0tGgUii6I=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250523120938-f1a248e5ec7f/go.mod h1:JPTpf7fpnojsSuwRJExhSZSy63pVbp3VM39+zj+sAJM=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10 h1:Pr7LDbjMANIQHl07zWgl1vDuhpsfDQUpZ8cX6DPabfg=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.10/go.mod h1:MtRM0ZZjR0kaDOzy9zWSt6/4/UlrnsNBq+1FNAF4vBk=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4 h1:Ec0x3hHR7xkld5Z09IGh16wtUUpBb2HgqZ9DExd8Q7s=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.4/go.mod h1:WP8+S0kqtnSbX1vlIpo5i8Irm/ijZITEPBcZ26B5unY=
|
||||
github.com/metacubex/sing v0.5.5 h1:m5U8iHvRAUxlme3FZlE/LPIGHjU8oMCUzXWGbQQAC1E=
|
||||
github.com/metacubex/sing v0.5.5/go.mod h1:ypf0mjwlZm0sKdQSY+yQvmsbWa0hNPtkeqyRMGgoN+w=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac h1:wDH/Jh/yqWbzPktqJP+Y1cUG8hchcrzKzUxJiSpnaQs=
|
||||
github.com/metacubex/sing-mux v0.3.3-0.20250813083925-d7c9aeaeeaac/go.mod h1:3rt1soewn0O6j89GCLmwAQFsq257u0jf2zQSPhTL3Bw=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb h1:U/m3h8lp/j7i8zFgfvScLdZa1/Y8dd74oO7iZaQq80s=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250718154553-1b193bec4cbb/go.mod h1:B60FxaPHjR1SeQB0IiLrgwgvKsaoASfOWdiqhLjmMGA=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12 h1:Wqzo8bYXrK5aWqxu/TjlTnYZzAKtKsaFQBdr6IHFaBE=
|
||||
github.com/metacubex/sing-shadowsocks v0.2.12/go.mod h1:2e5EIaw0rxKrm1YTRmiMnDulwbGxH9hAFlrwQLQMQkU=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6 h1:ZR1kYT0f0Vi64iQSS09OdhFfppiNkh7kjgRdMm0SB98=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.6/go.mod h1:vOEbfKC60txi0ca+yUlqEwOGc3Obl6cnSgx9Gf45KjE=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2 h1:gXU+MYPm7Wme3/OAY2FFzVq9d9GxPHOqu5AQfg/ddhI=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250503063515-5d9f966d17a2/go.mod h1:mbfboaXauKJNIHJYxQRa+NJs4JU9NZfkA+I33dS2+9E=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c h1:Y6jk7AH5BEg9Dsvczrf/KokYsvxeKSZZlCLHg+hC4ro=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250524142129-9d110c0af70c/go.mod h1:HDaHDL6onAX2ZGbAGUXKp++PohRdNb7Nzt6zxzhox+U=
|
||||
github.com/metacubex/sing-vmess v0.2.2 h1:nG6GIKF1UOGmlzs+BIetdGHkFZ20YqFVIYp5Htqzp+4=
|
||||
github.com/metacubex/sing-vmess v0.2.2/go.mod h1:CVDNcdSLVYFgTHQlubr88d8CdqupAUDqLjROos+H9xk=
|
||||
github.com/metacubex/sing-tun v0.4.7 h1:ZDY/W+1c7PeWWKeKRyUo18fySF/TWjB0i5ui81Ar778=
|
||||
github.com/metacubex/sing-tun v0.4.7/go.mod h1:xHecZRwBnKWe6zG9amAK9cXf91lF6blgjBqm+VvOrmU=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0 h1:WZepq4TOZa6WewB8tGAZrrL+bL2R2ivoBzuEgAeolWc=
|
||||
github.com/metacubex/sing-vmess v0.2.4-0.20250822020810-4856053566f0/go.mod h1:21R5R1u90uUvBQF0owoooEu96/SAYYD56nDrwm6nFaM=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f h1:Sr/DYKYofKHKc4GF3qkRGNuj6XA6c0eqPgEDN+VAsYU=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20250503063753-2dc62acc626f/go.mod h1:jpAkVLPnCpGSfNyVmj6Cq4YbuZsFepm/Dc+9BAOcR80=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee h1:lp6hJ+4wCLZu113awp7P6odM2okB5s60HUyF0FMqKmo=
|
||||
github.com/metacubex/smux v0.0.0-20250503055512-501391591dee/go.mod h1:4bPD8HWx9jPJ9aE4uadgyN7D1/Wz3KmPy+vale8sKLE=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4 h1:j1VRTiC9JLR4nUbSikx9OGdu/3AgFDqgcLj4GoqyQkc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250516165257-e29c16ae41d4/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.7.3 h1:yDcMEWojFh+t8rU9X0HPcZDPAoFze/rIIyssqivzj8A=
|
||||
github.com/metacubex/utls v1.7.3/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617 h1:yN3mQ4cT9sPUciw/rO0Isc/8QlO86DB6g9SEMRgQ8Cw=
|
||||
github.com/metacubex/tfo-go v0.0.0-20250827083229-aa432b865617/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142 h1:csEbKOzRAxJXffOeZnnS3/kA/F55JiTbKv5jcYqCXms=
|
||||
github.com/metacubex/utls v1.8.1-0.20250823120917-12f5ba126142/go.mod h1:67I3skhEY4Sya8f1YxELwWPoeQdXqZCrWNYLvq8gn2U=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f h1:FGBPRb1zUabhPhDrlKEjQ9lgIwQ6cHL4x8M9lrERhbk=
|
||||
github.com/metacubex/wireguard-go v0.0.0-20250820062549-a6cecdd7f57f/go.mod h1:oPGcV994OGJedmmxrcK9+ni7jUEMGhR+uVQAdaduIP4=
|
||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||
@@ -167,16 +167,14 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
|
||||
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis=
|
||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||
github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
|
||||
github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
|
||||
github.com/samber/lo v1.51.0 h1:kysRYLbHy/MB7kQZf5DSN50JHmMsNEdeY24VzJFu7wI=
|
||||
github.com/samber/lo v1.51.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||
@@ -198,8 +196,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
|
||||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
@@ -277,9 +275,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
|
||||
lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE=
|
||||
lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
|
||||
|
||||
@@ -260,6 +260,7 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
||||
DirectServer: c.DirectNameServer,
|
||||
DirectFollowPolicy: c.DirectFollowPolicy,
|
||||
CacheAlgorithm: c.CacheAlgorithm,
|
||||
CacheMaxSize: c.CacheMaxSize,
|
||||
}
|
||||
|
||||
r := dns.NewResolver(cfg)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
func cacheRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Post("/fakeip/flush", flushFakeIPPool)
|
||||
r.Post("/dns/flush", flushDnsCache)
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -24,3 +25,8 @@ func flushFakeIPPool(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
func flushDnsCache(w http.ResponseWriter, r *http.Request) {
|
||||
resolver.ClearCache()
|
||||
render.NoContent(w, r)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/process"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
@@ -33,28 +34,29 @@ func configRouter() http.Handler {
|
||||
}
|
||||
|
||||
type configSchema struct {
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
Tun *tunSchema `json:"tun"`
|
||||
TuicServer *tuicServerSchema `json:"tuic-server"`
|
||||
ShadowSocksConfig *string `json:"ss-config"`
|
||||
VmessConfig *string `json:"vmess-config"`
|
||||
TcptunConfig *string `json:"tcptun-config"`
|
||||
UdptunConfig *string `json:"udptun-config"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"`
|
||||
LanAllowedIPs *[]netip.Prefix `json:"lan-allowed-ips"`
|
||||
LanDisAllowedIPs *[]netip.Prefix `json:"lan-disallowed-ips"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Sniffing *bool `json:"sniffing"`
|
||||
TcpConcurrent *bool `json:"tcp-concurrent"`
|
||||
InterfaceName *string `json:"interface-name"`
|
||||
Port *int `json:"port"`
|
||||
SocksPort *int `json:"socks-port"`
|
||||
RedirPort *int `json:"redir-port"`
|
||||
TProxyPort *int `json:"tproxy-port"`
|
||||
MixedPort *int `json:"mixed-port"`
|
||||
Tun *tunSchema `json:"tun"`
|
||||
TuicServer *tuicServerSchema `json:"tuic-server"`
|
||||
ShadowSocksConfig *string `json:"ss-config"`
|
||||
VmessConfig *string `json:"vmess-config"`
|
||||
TcptunConfig *string `json:"tcptun-config"`
|
||||
UdptunConfig *string `json:"udptun-config"`
|
||||
AllowLan *bool `json:"allow-lan"`
|
||||
SkipAuthPrefixes *[]netip.Prefix `json:"skip-auth-prefixes"`
|
||||
LanAllowedIPs *[]netip.Prefix `json:"lan-allowed-ips"`
|
||||
LanDisAllowedIPs *[]netip.Prefix `json:"lan-disallowed-ips"`
|
||||
BindAddress *string `json:"bind-address"`
|
||||
Mode *tunnel.TunnelMode `json:"mode"`
|
||||
LogLevel *log.LogLevel `json:"log-level"`
|
||||
IPv6 *bool `json:"ipv6"`
|
||||
Sniffing *bool `json:"sniffing"`
|
||||
TcpConcurrent *bool `json:"tcp-concurrent"`
|
||||
FindProcessMode *process.FindProcessMode `json:"find-process-mode"`
|
||||
InterfaceName *string `json:"interface-name"`
|
||||
}
|
||||
|
||||
type tunSchema struct {
|
||||
@@ -75,6 +77,7 @@ type tunSchema struct {
|
||||
AutoRedirect *bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark *uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark *uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||
LoopbackAddress *[]netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||
StrictRoute *bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
RouteAddress *[]netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||
RouteAddressSet *[]string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||
@@ -97,6 +100,10 @@ type tunSchema struct {
|
||||
Inet6RouteAddress *[]netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress *[]netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress *[]netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
|
||||
// darwin special config
|
||||
RecvMsgX *bool `yaml:"recvmsgx" json:"recvmsgx,omitempty"`
|
||||
SendMsgX *bool `yaml:"sendmsgx" json:"sendmsgx,omitempty"`
|
||||
}
|
||||
|
||||
type tuicServerSchema struct {
|
||||
@@ -174,6 +181,9 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.AutoRedirectOutputMark != nil {
|
||||
def.AutoRedirectOutputMark = *p.AutoRedirectOutputMark
|
||||
}
|
||||
if p.LoopbackAddress != nil {
|
||||
def.LoopbackAddress = *p.LoopbackAddress
|
||||
}
|
||||
if p.StrictRoute != nil {
|
||||
def.StrictRoute = *p.StrictRoute
|
||||
}
|
||||
@@ -237,6 +247,12 @@ func pointerOrDefaultTun(p *tunSchema, def LC.Tun) LC.Tun {
|
||||
if p.FileDescriptor != nil {
|
||||
def.FileDescriptor = *p.FileDescriptor
|
||||
}
|
||||
if p.RecvMsgX != nil {
|
||||
def.RecvMsgX = *p.RecvMsgX
|
||||
}
|
||||
if p.SendMsgX != nil {
|
||||
def.SendMsgX = *p.SendMsgX
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
@@ -337,6 +353,10 @@ func patchConfigs(w http.ResponseWriter, r *http.Request) {
|
||||
tunnel.SetMode(*general.Mode)
|
||||
}
|
||||
|
||||
if general.FindProcessMode != nil {
|
||||
tunnel.SetFindProcessMode(*general.FindProcessMode)
|
||||
}
|
||||
|
||||
if general.LogLevel != nil {
|
||||
log.SetLevel(*general.LogLevel)
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ func startPipe(cfg *Config) {
|
||||
}
|
||||
}
|
||||
|
||||
func safeEuqal(a, b string) bool {
|
||||
func safeEqual(a, b string) bool {
|
||||
aBuf := utils.ImmutableBytesFromString(a)
|
||||
bBuf := utils.ImmutableBytesFromString(b)
|
||||
return subtle.ConstantTimeCompare(aBuf, bBuf) == 1
|
||||
@@ -311,7 +311,7 @@ func authentication(secret string) func(http.Handler) http.Handler {
|
||||
// Browser websocket not support custom header
|
||||
if r.Header.Get("Upgrade") == "websocket" && r.URL.Query().Get("token") != "" {
|
||||
token := r.URL.Query().Get("token")
|
||||
if !safeEuqal(token, secret) {
|
||||
if !safeEqual(token, secret) {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
render.JSON(w, r, ErrUnauthorized)
|
||||
return
|
||||
@@ -324,7 +324,7 @@ func authentication(secret string) func(http.Handler) http.Handler {
|
||||
bearer, token, found := strings.Cut(header, " ")
|
||||
|
||||
hasInvalidHeader := bearer != "Bearer"
|
||||
hasInvalidSecret := !found || !safeEuqal(token, secret)
|
||||
hasInvalidSecret := !found || !safeEqual(token, secret)
|
||||
if hasInvalidHeader || hasInvalidSecret {
|
||||
render.Status(r, http.StatusUnauthorized)
|
||||
render.JSON(w, r, ErrUnauthorized)
|
||||
|
||||
@@ -32,7 +32,11 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = updater.UpdateCore(execPath)
|
||||
query := r.URL.Query()
|
||||
channel := query.Get("channel")
|
||||
force := query.Get("force") == "true"
|
||||
|
||||
err = updater.DefaultCoreUpdater.Update(execPath, channel, force)
|
||||
if err != nil {
|
||||
log.Warnln("%s", err)
|
||||
render.Status(r, http.StatusInternalServerError)
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
@@ -31,7 +31,7 @@ type Listener struct {
|
||||
listeners []net.Listener
|
||||
tlsConfig *tlsC.Config
|
||||
userMap map[[32]byte]string
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
padding atomic.Pointer[padding.PaddingFactory]
|
||||
}
|
||||
|
||||
func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
|
||||
@@ -9,18 +9,6 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func StringSliceToNetipPrefixSlice(ss []string) ([]netip.Prefix, error) {
|
||||
lps := make([]netip.Prefix, 0, len(ss))
|
||||
for _, s := range ss {
|
||||
prefix, err := netip.ParsePrefix(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lps = append(lps, prefix)
|
||||
}
|
||||
return lps, nil
|
||||
}
|
||||
|
||||
type Tun struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Device string `yaml:"device" json:"device"`
|
||||
@@ -39,6 +27,7 @@ type Tun struct {
|
||||
AutoRedirect bool `yaml:"auto-redirect" json:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `yaml:"auto-redirect-input-mark" json:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `yaml:"auto-redirect-output-mark" json:"auto-redirect-output-mark,omitempty"`
|
||||
LoopbackAddress []netip.Addr `yaml:"loopback-address" json:"loopback-address,omitempty"`
|
||||
StrictRoute bool `yaml:"strict-route" json:"strict-route,omitempty"`
|
||||
RouteAddress []netip.Prefix `yaml:"route-address" json:"route-address,omitempty"`
|
||||
RouteAddressSet []string `yaml:"route-address-set" json:"route-address-set,omitempty"`
|
||||
@@ -65,6 +54,10 @@ type Tun struct {
|
||||
Inet6RouteAddress []netip.Prefix `yaml:"inet6-route-address" json:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `yaml:"inet4-route-exclude-address" json:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `yaml:"inet6-route-exclude-address" json:"inet6-route-exclude-address,omitempty"`
|
||||
|
||||
// darwin special config
|
||||
RecvMsgX bool `yaml:"recvmsgx" json:"recvmsgx,omitempty"`
|
||||
SendMsgX bool `yaml:"sendmsgx" json:"sendmsgx,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Tun) Sort() {
|
||||
@@ -142,6 +135,9 @@ func (t *Tun) Equal(other Tun) bool {
|
||||
if t.AutoRedirectOutputMark != other.AutoRedirectOutputMark {
|
||||
return false
|
||||
}
|
||||
if !slices.Equal(t.RouteAddress, other.RouteAddress) {
|
||||
return false
|
||||
}
|
||||
if t.StrictRoute != other.StrictRoute {
|
||||
return false
|
||||
}
|
||||
@@ -207,5 +203,12 @@ func (t *Tun) Equal(other Tun) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if t.RecvMsgX != other.RecvMsgX {
|
||||
return false
|
||||
}
|
||||
if t.SendMsgX != other.SendMsgX {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type VlessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VlessUser
|
||||
Decryption string
|
||||
WsPath string
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package inbound_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
@@ -10,16 +11,18 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/ech"
|
||||
"github.com/metacubex/mihomo/component/generater"
|
||||
"github.com/metacubex/mihomo/component/generator"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
@@ -30,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
var httpPath = "/inbound_test"
|
||||
var httpData = make([]byte, 10240)
|
||||
var httpData = make([]byte, 2*pool.RelayBufferSize)
|
||||
var remoteAddr = netip.MustParseAddr("1.2.3.4")
|
||||
var userUUID = utils.NewUUIDV4().String()
|
||||
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = ca.NewRandomTLSKeyPair(ca.KeyPairTypeP256)
|
||||
@@ -46,13 +49,12 @@ var echConfigBase64, echKeyPem, _ = ech.GenECHConfig(echPublicSni)
|
||||
|
||||
func init() {
|
||||
rand.Read(httpData)
|
||||
privateKey, err := generater.GeneratePrivateKey()
|
||||
privateKey, err := generator.GenX25519PrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:])
|
||||
realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:])
|
||||
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey.Bytes())
|
||||
realityPublickey = base64.RawURLEncoding.EncodeToString(privateKey.PublicKey().Bytes())
|
||||
}
|
||||
|
||||
type TestTunnel struct {
|
||||
@@ -134,14 +136,22 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
render.Data(w, r, httpData)
|
||||
query := r.URL.Query()
|
||||
size, err := strconv.Atoi(query.Get("size"))
|
||||
if err != nil {
|
||||
render.Status(r, http.StatusBadRequest)
|
||||
render.PlainText(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
io.Copy(io.Discard, r.Body)
|
||||
render.Data(w, r, httpData[:size])
|
||||
})
|
||||
h2Server := &http2.Server{}
|
||||
server := http.Server{Handler: r}
|
||||
_ = http2.ConfigureServer(&server, h2Server)
|
||||
go server.Serve(ln)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
|
||||
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string, size int) {
|
||||
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s?size=%d", proto, remoteAddr, httpPath, size), bytes.NewReader(httpData[:size]))
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
@@ -200,7 +210,7 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
assert.Equal(t, httpData, data)
|
||||
assert.Equal(t, httpData[:size], data)
|
||||
}
|
||||
tunnel := &TestTunnel{
|
||||
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
|
||||
@@ -241,27 +251,30 @@ func NewHttpTestTunnel() *TestTunnel {
|
||||
},
|
||||
CloseFn: ln.Close,
|
||||
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
|
||||
|
||||
// Sequential testing for debugging
|
||||
t.Run("Sequential", func(t *testing.T) {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "http", len(httpData))
|
||||
testFn(t, proxy, "https", len(httpData))
|
||||
})
|
||||
|
||||
// Concurrent testing to detect stress
|
||||
t.Run("Concurrent", func(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
const num = 50
|
||||
for i := 0; i < num; i++ {
|
||||
num := len(httpData) / 1024
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "https")
|
||||
testFn(t, proxy, "https", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
for i := 0; i < num; i++ {
|
||||
for i := 1; i <= num; i++ {
|
||||
i := i
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
testFn(t, proxy, "http")
|
||||
testFn(t, proxy, "http", i*1024)
|
||||
defer wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
|
||||
var singMuxProtocolList = []string{"smux"} // don't test "h2mux" and "yamux" because it has some confused bugs
|
||||
|
||||
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
|
||||
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.
|
||||
|
||||
@@ -9,6 +9,15 @@ type RealityConfig struct {
|
||||
ServerNames []string `inbound:"server-names"`
|
||||
MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
|
||||
Proxy string `inbound:"proxy,omitempty"`
|
||||
|
||||
LimitFallbackUpload RealityLimitFallback `inbound:"limit-fallback-upload,omitempty"`
|
||||
LimitFallbackDownload RealityLimitFallback `inbound:"limit-fallback-download,omitempty"`
|
||||
}
|
||||
|
||||
type RealityLimitFallback struct {
|
||||
AfterBytes uint64 `inbound:"after-bytes,omitempty"`
|
||||
BytesPerSec uint64 `inbound:"bytes-per-sec,omitempty"`
|
||||
BurstBytesPerSec uint64 `inbound:"burst-bytes-per-sec,omitempty"`
|
||||
}
|
||||
|
||||
func (c RealityConfig) Build() reality.Config {
|
||||
@@ -19,5 +28,16 @@ func (c RealityConfig) Build() reality.Config {
|
||||
ServerNames: c.ServerNames,
|
||||
MaxTimeDifference: c.MaxTimeDifference,
|
||||
Proxy: c.Proxy,
|
||||
|
||||
LimitFallbackUpload: reality.LimitFallback{
|
||||
AfterBytes: c.LimitFallbackUpload.AfterBytes,
|
||||
BytesPerSec: c.LimitFallbackUpload.BytesPerSec,
|
||||
BurstBytesPerSec: c.LimitFallbackUpload.BurstBytesPerSec,
|
||||
},
|
||||
LimitFallbackDownload: reality.LimitFallback{
|
||||
AfterBytes: c.LimitFallbackDownload.AfterBytes,
|
||||
BytesPerSec: c.LimitFallbackDownload.BytesPerSec,
|
||||
BurstBytesPerSec: c.LimitFallbackDownload.BurstBytesPerSec,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"encoding"
|
||||
"net/netip"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
@@ -12,50 +12,59 @@ import (
|
||||
|
||||
type TunOption struct {
|
||||
BaseOption
|
||||
Device string `inbound:"device,omitempty"`
|
||||
Stack string `inbound:"stack,omitempty"`
|
||||
DNSHijack []string `inbound:"dns-hijack,omitempty"`
|
||||
AutoRoute bool `inbound:"auto-route,omitempty"`
|
||||
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
|
||||
Device string `inbound:"device,omitempty"`
|
||||
Stack C.TUNStack `inbound:"stack,omitempty"`
|
||||
DNSHijack []string `inbound:"dns-hijack,omitempty"`
|
||||
AutoRoute bool `inbound:"auto-route,omitempty"`
|
||||
AutoDetectInterface bool `inbound:"auto-detect-interface,omitempty"`
|
||||
|
||||
MTU uint32 `inbound:"mtu,omitempty"`
|
||||
GSO bool `inbound:"gso,omitempty"`
|
||||
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
|
||||
Inet4Address []string `inbound:"inet4-address,omitempty"`
|
||||
Inet6Address []string `inbound:"inet6-address,omitempty"`
|
||||
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
|
||||
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
|
||||
AutoRedirect bool `inbound:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
|
||||
StrictRoute bool `inbound:"strict-route,omitempty"`
|
||||
RouteAddress []string `inbound:"route-address,omitempty"`
|
||||
RouteAddressSet []string `inbound:"route-address-set,omitempty"`
|
||||
RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"`
|
||||
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
|
||||
IncludeInterface []string `inbound:"include-interface,omitempty"`
|
||||
ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
|
||||
IncludeUID []uint32 `inbound:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
|
||||
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
|
||||
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
|
||||
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
|
||||
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
|
||||
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
|
||||
IncludePackage []string `inbound:"include-package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||
MTU uint32 `inbound:"mtu,omitempty"`
|
||||
GSO bool `inbound:"gso,omitempty"`
|
||||
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
|
||||
Inet4Address []netip.Prefix `inbound:"inet4-address,omitempty"`
|
||||
Inet6Address []netip.Prefix `inbound:"inet6-address,omitempty"`
|
||||
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
|
||||
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
|
||||
AutoRedirect bool `inbound:"auto-redirect,omitempty"`
|
||||
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
|
||||
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
|
||||
LoopbackAddress []netip.Addr `inbound:"loopback-address,omitempty"`
|
||||
StrictRoute bool `inbound:"strict-route,omitempty"`
|
||||
RouteAddress []netip.Prefix `inbound:"route-address,omitempty"`
|
||||
RouteAddressSet []string `inbound:"route-address-set,omitempty"`
|
||||
RouteExcludeAddress []netip.Prefix `inbound:"route-exclude-address,omitempty"`
|
||||
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
|
||||
IncludeInterface []string `inbound:"include-interface,omitempty"`
|
||||
ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
|
||||
IncludeUID []uint32 `inbound:"include-uid,omitempty"`
|
||||
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
|
||||
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
|
||||
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
|
||||
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
|
||||
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
|
||||
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
|
||||
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
|
||||
IncludePackage []string `inbound:"include-package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||
EndpointIndependentNat bool `inbound:"endpoint-independent-nat,omitempty"`
|
||||
UDPTimeout int64 `inbound:"udp-timeout,omitempty"`
|
||||
FileDescriptor int `inbound:"file-descriptor,omitempty"`
|
||||
|
||||
Inet4RouteAddress []string `inbound:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress []string `inbound:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress []string `inbound:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress []string `inbound:"inet6-route-exclude-address,omitempty"`
|
||||
Inet4RouteAddress []netip.Prefix `inbound:"inet4-route-address,omitempty"`
|
||||
Inet6RouteAddress []netip.Prefix `inbound:"inet6-route-address,omitempty"`
|
||||
Inet4RouteExcludeAddress []netip.Prefix `inbound:"inet4-route-exclude-address,omitempty"`
|
||||
Inet6RouteExcludeAddress []netip.Prefix `inbound:"inet6-route-exclude-address,omitempty"`
|
||||
|
||||
// darwin special config
|
||||
RecvMsgX bool `inbound:"recvmsgx,omitempty"`
|
||||
SendMsgX bool `inbound:"sendmsgx,omitempty"`
|
||||
}
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*netip.Addr)(nil) // ensure netip.Addr can decode direct by structure package
|
||||
var _ encoding.TextUnmarshaler = (*netip.Prefix)(nil) // ensure netip.Prefix can decode direct by structure package
|
||||
var _ encoding.TextUnmarshaler = (*C.TUNStack)(nil) // ensure C.TUNStack can decode direct by structure package
|
||||
|
||||
func (o TunOption) Equal(config C.InboundConfig) bool {
|
||||
return optionToString(o) == optionToString(config)
|
||||
}
|
||||
@@ -72,68 +81,31 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stack, exist := C.StackTypeMapping[strings.ToLower(options.Stack)]
|
||||
if !exist {
|
||||
return nil, errors.New("invalid tun stack")
|
||||
}
|
||||
|
||||
routeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
routeExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.RouteExcludeAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inet4Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet4Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inet6Address, err := LC.StringSliceToNetipPrefixSlice(options.Inet6Address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inet4RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inet6RouteAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inet4RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet4RouteExcludeAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inet6RouteExcludeAddress, err := LC.StringSliceToNetipPrefixSlice(options.Inet6RouteExcludeAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Tun{
|
||||
Base: base,
|
||||
config: options,
|
||||
tun: LC.Tun{
|
||||
Enable: true,
|
||||
Device: options.Device,
|
||||
Stack: stack,
|
||||
Stack: options.Stack,
|
||||
DNSHijack: options.DNSHijack,
|
||||
AutoRoute: options.AutoRoute,
|
||||
AutoDetectInterface: options.AutoDetectInterface,
|
||||
MTU: options.MTU,
|
||||
GSO: options.GSO,
|
||||
GSOMaxSize: options.GSOMaxSize,
|
||||
Inet4Address: inet4Address,
|
||||
Inet6Address: inet6Address,
|
||||
Inet4Address: options.Inet4Address,
|
||||
Inet6Address: options.Inet6Address,
|
||||
IPRoute2TableIndex: options.IPRoute2TableIndex,
|
||||
IPRoute2RuleIndex: options.IPRoute2RuleIndex,
|
||||
AutoRedirect: options.AutoRedirect,
|
||||
AutoRedirectInputMark: options.AutoRedirectInputMark,
|
||||
AutoRedirectOutputMark: options.AutoRedirectOutputMark,
|
||||
LoopbackAddress: options.LoopbackAddress,
|
||||
StrictRoute: options.StrictRoute,
|
||||
RouteAddress: routeAddress,
|
||||
RouteAddress: options.RouteAddress,
|
||||
RouteAddressSet: options.RouteAddressSet,
|
||||
RouteExcludeAddress: routeExcludeAddress,
|
||||
RouteExcludeAddress: options.RouteExcludeAddress,
|
||||
RouteExcludeAddressSet: options.RouteExcludeAddressSet,
|
||||
IncludeInterface: options.IncludeInterface,
|
||||
ExcludeInterface: options.ExcludeInterface,
|
||||
@@ -152,10 +124,13 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||
UDPTimeout: options.UDPTimeout,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
|
||||
Inet4RouteAddress: inet4RouteAddress,
|
||||
Inet6RouteAddress: inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: inet6RouteExcludeAddress,
|
||||
Inet4RouteAddress: options.Inet4RouteAddress,
|
||||
Inet6RouteAddress: options.Inet6RouteAddress,
|
||||
Inet4RouteExcludeAddress: options.Inet4RouteExcludeAddress,
|
||||
Inet6RouteExcludeAddress: options.Inet6RouteExcludeAddress,
|
||||
|
||||
RecvMsgX: options.RecvMsgX,
|
||||
SendMsgX: options.SendMsgX,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
type VlessOption struct {
|
||||
BaseOption
|
||||
Users []VlessUser `inbound:"users"`
|
||||
Decryption string `inbound:"decryption,omitempty"`
|
||||
WsPath string `inbound:"ws-path,omitempty"`
|
||||
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
@@ -58,6 +59,7 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Users: users,
|
||||
Decryption: options.Decryption,
|
||||
WsPath: options.WsPath,
|
||||
GrpcServiceName: options.GrpcServiceName,
|
||||
Certificate: options.Certificate,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/listener/inbound"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -87,6 +88,41 @@ func TestInboundVless_TLS(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestInboundVless_Encryption(t *testing.T) {
|
||||
seedBase64, clientBase64, _, err := encryption.GenMLKEM768("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
privateKeyBase64, passwordBase64, _, err := encryption.GenX25519("")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
var modes = []string{
|
||||
"native",
|
||||
"xorpub",
|
||||
"random",
|
||||
}
|
||||
for i := range modes {
|
||||
mode := modes[i]
|
||||
t.Run(mode, func(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Decryption: "mlkem768x25519plus." + mode + ".600s." + privateKeyBase64 + "." + seedBase64,
|
||||
}
|
||||
outboundOptions := outbound.VlessOption{
|
||||
Encryption: "mlkem768x25519plus." + mode + ".0rtt." + passwordBase64 + "." + clientBase64,
|
||||
}
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||
outboundOptions := outboundOptions
|
||||
outboundOptions.Flow = "xtls-rprx-vision"
|
||||
testInboundVless(t, inboundOptions, outboundOptions)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundVless_Wss1(t *testing.T) {
|
||||
inboundOptions := inbound.VlessOption{
|
||||
Certificate: tlsCertificate,
|
||||
|
||||
@@ -64,7 +64,7 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
|
||||
listener, err = IN.NewTunnel(tunnelOption)
|
||||
case "tun":
|
||||
tunOption := &IN.TunOption{
|
||||
Stack: C.TunGvisor.String(),
|
||||
Stack: C.TunGvisor,
|
||||
DNSHijack: []string{"0.0.0.0:53"}, // default hijack all dns query
|
||||
}
|
||||
err = decoder.Decode(mapping, tunOption)
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
)
|
||||
|
||||
type Conn = utls.Conn
|
||||
type LimitFallback = utls.RealityLimitFallback
|
||||
|
||||
type Config struct {
|
||||
Dest string
|
||||
@@ -28,6 +29,9 @@ type Config struct {
|
||||
ServerNames []string
|
||||
MaxTimeDifference int
|
||||
Proxy string
|
||||
|
||||
LimitFallbackUpload LimitFallback
|
||||
LimitFallbackDownload LimitFallback
|
||||
}
|
||||
|
||||
func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
|
||||
@@ -73,6 +77,9 @@ func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
|
||||
return inner.HandleTcp(tunnel, address, c.Proxy)
|
||||
}
|
||||
|
||||
realityConfig.LimitFallbackUpload = c.LimitFallbackUpload
|
||||
realityConfig.LimitFallbackDownload = c.LimitFallbackDownload
|
||||
|
||||
return &Builder{realityConfig}, nil
|
||||
}
|
||||
|
||||
@@ -106,3 +113,11 @@ func (c realityConnWrapper) Upstream() any {
|
||||
func (c realityConnWrapper) CloseWrite() error {
|
||||
return c.Close()
|
||||
}
|
||||
|
||||
func (c realityConnWrapper) ReaderReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c realityConnWrapper) WriterReplaceable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
mux "github.com/metacubex/sing-mux"
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
"github.com/metacubex/sing-vmess/packetaddr"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/buf"
|
||||
"github.com/metacubex/sing/common/bufio"
|
||||
@@ -145,6 +146,10 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
|
||||
}
|
||||
|
||||
func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.PacketConn, metadata M.Metadata) error {
|
||||
if metadata.Destination.Fqdn == packetaddr.SeqPacketMagicAddress {
|
||||
conn = packetaddr.NewConn(bufio.NewNetPacketConn(conn), M.Socksaddr{})
|
||||
}
|
||||
|
||||
defer func() { _ = conn.Close() }()
|
||||
mutex := sync.Mutex{}
|
||||
writer := bufio.NewNetPacketWriter(conn) // a new interface to set nil in defer
|
||||
|
||||
@@ -347,6 +347,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
IPRoute2RuleIndex: ruleIndex,
|
||||
AutoRedirectInputMark: inputMark,
|
||||
AutoRedirectOutputMark: outputMark,
|
||||
Inet4LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is4),
|
||||
Inet6LoopbackAddress: common.Filter(options.LoopbackAddress, netip.Addr.Is6),
|
||||
StrictRoute: options.StrictRoute,
|
||||
Inet4RouteAddress: inet4RouteAddress,
|
||||
Inet6RouteAddress: inet6RouteAddress,
|
||||
@@ -363,6 +365,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
FileDescriptor: options.FileDescriptor,
|
||||
InterfaceMonitor: defaultInterfaceMonitor,
|
||||
EXP_RecvMsgX: options.RecvMsgX,
|
||||
EXP_SendMsgX: options.SendMsgX,
|
||||
}
|
||||
|
||||
if options.AutoRedirect {
|
||||
|
||||
@@ -19,16 +19,18 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/vless/encryption"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
"github.com/metacubex/sing-vmess/vless"
|
||||
"github.com/metacubex/sing/common"
|
||||
"github.com/metacubex/sing/common/metadata"
|
||||
"github.com/metacubex/sing/common/network"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*reality.Conn](conn) // *utls.Conn
|
||||
tlsConn, loaded := network.CastReader[*reality.Conn](conn) // *utls.Conn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
@@ -36,19 +38,28 @@ func init() {
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*tlsC.UConn](conn) // *utls.UConn
|
||||
tlsConn, loaded := network.CastReader[*tlsC.UConn](conn) // *utls.UConn
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := network.CastReader[*encryption.CommonConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.Conn, reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn)
|
||||
})
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
decryption *encryption.ServerInstance
|
||||
}
|
||||
|
||||
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
@@ -80,7 +91,20 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
return it.Flow
|
||||
}))
|
||||
|
||||
sl = &Listener{false, config, nil, service}
|
||||
sl = &Listener{config: config, service: service}
|
||||
|
||||
sl.decryption, err = encryption.NewServer(config.Decryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sl.decryption != nil {
|
||||
defer func() { // decryption must be closed to avoid the goroutine leak
|
||||
if err != nil {
|
||||
_ = sl.decryption.Close()
|
||||
sl.decryption = nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
tlsConfig := &tlsC.Config{}
|
||||
var realityBuilder *reality.Builder
|
||||
@@ -149,8 +173,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
} else {
|
||||
l = tlsC.NewListener(l, tlsConfig)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
||||
} else if sl.decryption == nil {
|
||||
return nil, errors.New("disallow using Vless without any certificates/reality/decryption config")
|
||||
}
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
@@ -185,6 +209,9 @@ func (l *Listener) Close() error {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
if l.decryption != nil {
|
||||
_ = l.decryption.Close()
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
@@ -201,6 +228,13 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||
|
||||
func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||
if l.decryption != nil {
|
||||
var err error
|
||||
conn, err = l.decryption.Handshake(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||
Protocol: "vless",
|
||||
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
|
||||
35
log/level.go
35
log/level.go
@@ -1,7 +1,6 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
@@ -25,30 +24,6 @@ const (
|
||||
|
||||
type LogLevel int
|
||||
|
||||
// UnmarshalYAML unserialize LogLevel with yaml
|
||||
func (l *LogLevel) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
var tp string
|
||||
unmarshal(&tp)
|
||||
level, exist := LogLevelMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid log-level")
|
||||
}
|
||||
*l = level
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unserialize LogLevel with json
|
||||
func (l *LogLevel) UnmarshalJSON(data []byte) error {
|
||||
var tp string
|
||||
json.Unmarshal(data, &tp)
|
||||
level, exist := LogLevelMapping[strings.ToLower(tp)]
|
||||
if !exist {
|
||||
return errors.New("invalid log-level")
|
||||
}
|
||||
*l = level
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalText unserialize LogLevel
|
||||
func (l *LogLevel) UnmarshalText(data []byte) error {
|
||||
level, exist := LogLevelMapping[strings.ToLower(string(data))]
|
||||
@@ -59,16 +34,6 @@ func (l *LogLevel) UnmarshalText(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalYAML serialize LogLevel with yaml
|
||||
func (l LogLevel) MarshalYAML() (any, error) {
|
||||
return l.String(), nil
|
||||
}
|
||||
|
||||
// MarshalJSON serialize LogLevel with json
|
||||
func (l LogLevel) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(l.String())
|
||||
}
|
||||
|
||||
// MarshalText serialize LogLevel
|
||||
func (l LogLevel) MarshalText() ([]byte, error) {
|
||||
return []byte(l.String()), nil
|
||||
|
||||
4
main.go
4
main.go
@@ -14,7 +14,7 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/metacubex/mihomo/component/generater"
|
||||
"github.com/metacubex/mihomo/component/generator"
|
||||
"github.com/metacubex/mihomo/component/geodata"
|
||||
"github.com/metacubex/mihomo/component/updater"
|
||||
"github.com/metacubex/mihomo/config"
|
||||
@@ -73,7 +73,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 && os.Args[1] == "generate" {
|
||||
generater.Main(os.Args[2:])
|
||||
generator.Main(os.Args[2:])
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package common
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
@@ -21,14 +22,6 @@ var (
|
||||
type Base struct {
|
||||
}
|
||||
|
||||
func (b *Base) ShouldFindProcess() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Base) ShouldResolveIP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (b *Base) ProviderNames() []string { return nil }
|
||||
|
||||
func ParseParams(params []string) (isSrc bool, noResolve bool) {
|
||||
@@ -41,4 +34,48 @@ func ParseParams(params []string) (isSrc bool, noResolve bool) {
|
||||
return
|
||||
}
|
||||
|
||||
func trimArr(arr []string) (r []string) {
|
||||
for _, e := range arr {
|
||||
r = append(r, strings.Trim(e, " "))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseRulePayload parse rule format like:
|
||||
// `tp,payload,target(,params...)` or `tp,payload(,params...)`
|
||||
// needTarget control the format contains `target` in string
|
||||
func ParseRulePayload(ruleRaw string, needTarget bool) (tp, payload, target string, params []string) {
|
||||
item := trimArr(strings.Split(ruleRaw, ","))
|
||||
tp = strings.ToUpper(item[0])
|
||||
if len(item) > 1 {
|
||||
switch tp {
|
||||
case "MATCH":
|
||||
// MATCH doesn't contain payload and params
|
||||
target = item[1]
|
||||
case "NOT", "OR", "AND", "SUB-RULE", "DOMAIN-REGEX", "PROCESS-NAME-REGEX", "PROCESS-PATH-REGEX":
|
||||
// some type of rules that has comma in payload and don't need params
|
||||
if needTarget {
|
||||
l := len(item)
|
||||
target = item[l-1] // don't have params so target must at the end of slices
|
||||
item = item[:l-1] // remove the target from slices
|
||||
}
|
||||
payload = strings.Join(item[1:], ",")
|
||||
default:
|
||||
payload = item[1]
|
||||
if len(item) > 2 {
|
||||
if needTarget {
|
||||
target = item[2]
|
||||
if len(item) > 3 {
|
||||
params = item[3:]
|
||||
}
|
||||
} else {
|
||||
params = item[2:]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type ParseRuleFunc func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (C.Rule, error)
|
||||
|
||||
@@ -17,7 +17,7 @@ func (d *Domain) RuleType() C.RuleType {
|
||||
return C.Domain
|
||||
}
|
||||
|
||||
func (d *Domain) Match(metadata *C.Metadata) (bool, string) {
|
||||
func (d *Domain) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
|
||||
return metadata.RuleHost() == d.domain, d.adapter
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ func (dk *DomainKeyword) RuleType() C.RuleType {
|
||||
return C.DomainKeyword
|
||||
}
|
||||
|
||||
func (dk *DomainKeyword) Match(metadata *C.Metadata) (bool, string) {
|
||||
func (dk *DomainKeyword) Match(metadata *C.Metadata, helper C.RuleMatchHelper) (bool, string) {
|
||||
domain := metadata.RuleHost()
|
||||
return strings.Contains(domain, dk.keyword), dk.adapter
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user