mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-01 01:59:55 +00:00
Compare commits
326 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bc6f77e36 | ||
|
|
a7e56f1c43 | ||
|
|
05e8f13a8d | ||
|
|
136d114196 | ||
|
|
938ab7f44d | ||
|
|
a00f4f1108 | ||
|
|
1213023f11 | ||
|
|
3b40bf76b7 | ||
|
|
1dc4155195 | ||
|
|
d81c19a7c8 | ||
|
|
e2140e62ca | ||
|
|
8d783c65c1 | ||
|
|
91324b76d2 | ||
|
|
e23f40a56b | ||
|
|
5830afcbde | ||
|
|
e2b75b35bb | ||
|
|
06b9e6c367 | ||
|
|
dc1145a484 | ||
|
|
b151e7d69c | ||
|
|
808fdcf624 | ||
|
|
9962a0d091 | ||
|
|
ef29e4501e | ||
|
|
d1d846f1ab | ||
|
|
eaaccbc6dd | ||
|
|
447c416391 | ||
|
|
6d24ca9ae6 | ||
|
|
b52b7537fc | ||
|
|
3cc67fd759 | ||
|
|
9074b78e36 | ||
|
|
ccc3f84da2 | ||
|
|
9bfb10d7ae | ||
|
|
0a5ea37c07 | ||
|
|
a440f64080 | ||
|
|
0ac6c3b185 | ||
|
|
b69e52d4d7 | ||
|
|
9c73b5b750 | ||
|
|
fc233184fd | ||
|
|
192d769f75 | ||
|
|
c99c71a969 | ||
|
|
c7661d7765 | ||
|
|
56c128880c | ||
|
|
f4806b49b4 | ||
|
|
49d54cc293 | ||
|
|
1c5f4a3ab1 | ||
|
|
368b1e1296 | ||
|
|
a9ce5da09d | ||
|
|
301c78ff9a | ||
|
|
72a126e580 | ||
|
|
20739f5db7 | ||
|
|
89dfabe9b3 | ||
|
|
5a9ad0ed3c | ||
|
|
bb803249fa | ||
|
|
3f6823ba49 | ||
|
|
c786b72030 | ||
|
|
269c52575c | ||
|
|
c7fc93df37 | ||
|
|
5d9d8f4d3b | ||
|
|
f3a43fe3a6 | ||
|
|
9a959202ed | ||
|
|
cd23112dc5 | ||
|
|
613becd8ea | ||
|
|
d6b496d3c0 | ||
|
|
5a24efdabf | ||
|
|
9de9f1ef51 | ||
|
|
fbead56ec9 | ||
|
|
1fff34d30e | ||
|
|
a35f712478 | ||
|
|
f805a9f4c6 | ||
|
|
eb985b002e | ||
|
|
462343531e | ||
|
|
671d901ee2 | ||
|
|
80e4eaad14 | ||
|
|
25b3c86d31 | ||
|
|
de19f927e8 | ||
|
|
792f16265e | ||
|
|
215bf0995f | ||
|
|
91d54bdac1 | ||
|
|
ce52c3438b | ||
|
|
d4478dbfa2 | ||
|
|
69454b030e | ||
|
|
e6d1c8cedf | ||
|
|
fabd216c34 | ||
|
|
a86c562852 | ||
|
|
3e966e82c7 | ||
|
|
b9171ade7f | ||
|
|
95af5f7325 | ||
|
|
ca3f1ebae6 | ||
|
|
4437c8861c | ||
|
|
57725078e0 | ||
|
|
08dcef80bf | ||
|
|
9fd63fe938 | ||
|
|
8e6eb70e71 | ||
|
|
9937ae1002 | ||
|
|
8f5a86410c | ||
|
|
9286e21026 | ||
|
|
c63a851bba | ||
|
|
4a16d22398 | ||
|
|
990de84391 | ||
|
|
ecd8facd81 | ||
|
|
a330fa1506 | ||
|
|
fc9d5cfee9 | ||
|
|
264713571d | ||
|
|
a67c379884 | ||
|
|
af5ad3254b | ||
|
|
acfc9f8baa | ||
|
|
1633885794 | ||
|
|
2afa2798b1 | ||
|
|
cd2d1c6bb0 | ||
|
|
88bfe7cffe | ||
|
|
43cb48231a | ||
|
|
4fa15c6334 | ||
|
|
5812a7bdeb | ||
|
|
3922b17067 | ||
|
|
a4e84f0479 | ||
|
|
6c0383026e | ||
|
|
59a2b24593 | ||
|
|
966eeae41b | ||
|
|
150c6ccd25 | ||
|
|
33823f1728 | ||
|
|
781b783346 | ||
|
|
ddfa9e8671 | ||
|
|
b7cb6774bf | ||
|
|
5d242510c8 | ||
|
|
223eae0e06 | ||
|
|
7dafe7889e | ||
|
|
d80e8bb0c2 | ||
|
|
f52fe6aa74 | ||
|
|
a08aa10630 | ||
|
|
794645b7f8 | ||
|
|
f020b20ab9 | ||
|
|
3676d1b79f | ||
|
|
58c973ee2b | ||
|
|
fb4d3c41c8 | ||
|
|
e33d4a4769 | ||
|
|
4c3fe98ebd | ||
|
|
0a2f606e1b | ||
|
|
8230bc8e7d | ||
|
|
ecbbf9d220 | ||
|
|
f305e440ef | ||
|
|
910f236696 | ||
|
|
417d709d60 | ||
|
|
f8557f5fd8 | ||
|
|
89b9438fc0 | ||
|
|
7c8f451892 | ||
|
|
ef244b896a | ||
|
|
595a575cde | ||
|
|
b1301b1b41 | ||
|
|
dc29514fb6 | ||
|
|
8940bdd56f | ||
|
|
ade4234615 | ||
|
|
faaa90f8a6 | ||
|
|
43f21c0b41 | ||
|
|
56fe7d5304 | ||
|
|
802267fb5b | ||
|
|
6306c6b580 | ||
|
|
f6164ac195 | ||
|
|
08ac9a3fae | ||
|
|
38fd37108b | ||
|
|
763a127287 | ||
|
|
a96f72ade4 | ||
|
|
4fecf68b8b | ||
|
|
8483178524 | ||
|
|
3e2c9ce821 | ||
|
|
6e04e1e9dc | ||
|
|
d79423a4fa | ||
|
|
9cf3eb39f5 | ||
|
|
81756fc927 | ||
|
|
518e9bdb0b | ||
|
|
27bcb26ecd | ||
|
|
53425bb9f2 | ||
|
|
f5834dd5e2 | ||
|
|
16c95fca87 | ||
|
|
41efc5e5ab | ||
|
|
c4660e1aad | ||
|
|
512d188384 | ||
|
|
ba605b693b | ||
|
|
c8380335cb | ||
|
|
82984891e4 | ||
|
|
0793998de8 | ||
|
|
6bf419c5fe | ||
|
|
4fedfc47b0 | ||
|
|
92ec5f2236 | ||
|
|
4c10d42fbf | ||
|
|
7fd0467aef | ||
|
|
696b75ee37 | ||
|
|
f20f371a61 | ||
|
|
24c6e7d819 | ||
|
|
acaacd8ab1 | ||
|
|
12c5cf361d | ||
|
|
50d0cd363c | ||
|
|
5bf22422d9 | ||
|
|
c17d7c0281 | ||
|
|
951cae2156 | ||
|
|
4f339265d3 | ||
|
|
ea4181308d | ||
|
|
030631607e | ||
|
|
beefe37260 | ||
|
|
5a73d99c6f | ||
|
|
e4646fc3d2 | ||
|
|
bb554e89d9 | ||
|
|
fd205bfa8d | ||
|
|
e7e1400126 | ||
|
|
117cdd8b54 | ||
|
|
c830b8aaf7 | ||
|
|
1db3e4583b | ||
|
|
4f8a5a5f54 | ||
|
|
303f6e4567 | ||
|
|
0d90a93645 | ||
|
|
40c9829328 | ||
|
|
4051ea522a | ||
|
|
cc7823dad8 | ||
|
|
d6a1af23a7 | ||
|
|
313493cc94 | ||
|
|
4b9fdacbad | ||
|
|
13b7ab8da3 | ||
|
|
de61e81ff7 | ||
|
|
4eb13a73bf | ||
|
|
fd5b537ab1 | ||
|
|
28794c62c4 | ||
|
|
e263518f01 | ||
|
|
345061a7cc | ||
|
|
a05016a5da | ||
|
|
9e3589d638 | ||
|
|
fc03bd2f0d | ||
|
|
0e228765fc | ||
|
|
f45ccc0761 | ||
|
|
9f4cd646c2 | ||
|
|
a9ecc627e6 | ||
|
|
50286678bf | ||
|
|
917c5fdd80 | ||
|
|
1457f83530 | ||
|
|
5ab8154e7e | ||
|
|
09be5cbc99 | ||
|
|
0738e18100 | ||
|
|
40f40f6d24 | ||
|
|
ad5bc51c77 | ||
|
|
75c16f9b87 | ||
|
|
d96d7651ca | ||
|
|
a5f25a2246 | ||
|
|
2b4741fbc7 | ||
|
|
f317baa8de | ||
|
|
5678131591 | ||
|
|
10f8ba4434 | ||
|
|
cacfefad4b | ||
|
|
0d4e57cb21 | ||
|
|
063836fe5d | ||
|
|
7b3c9e94e6 | ||
|
|
be3d121ec6 | ||
|
|
59fd3cffe3 | ||
|
|
39eda257a7 | ||
|
|
d3fea909e9 | ||
|
|
7eb70aeb4d | ||
|
|
846bdfa812 | ||
|
|
0b6ae6ffb8 | ||
|
|
71922dd0b1 | ||
|
|
5eb8958ff2 | ||
|
|
ac2506154f | ||
|
|
43bdc76f87 | ||
|
|
3195c678c7 | ||
|
|
bd43eca09d | ||
|
|
c504985b99 | ||
|
|
4243a74284 | ||
|
|
e749c7e492 | ||
|
|
c3ee921d30 | ||
|
|
df69a31e62 | ||
|
|
b7c02a5923 | ||
|
|
bfb6caeeaf | ||
|
|
30a913aad6 | ||
|
|
00e361c5ac | ||
|
|
56edd8f671 | ||
|
|
2b52809d2c | ||
|
|
5c3a9b1dfc | ||
|
|
fe88f0e437 | ||
|
|
87877d1b80 | ||
|
|
1bc3c16b59 | ||
|
|
ed1e7e32c7 | ||
|
|
5da9ccaa98 | ||
|
|
fd7ecc004f | ||
|
|
3ae4014b39 | ||
|
|
a50339bd5f | ||
|
|
7df1c26942 | ||
|
|
fc82a32a48 | ||
|
|
adf0ff588f | ||
|
|
b840eae4c6 | ||
|
|
619f34119e | ||
|
|
6d1c62bbf0 | ||
|
|
5dd883e790 | ||
|
|
a2b43faa0b | ||
|
|
8861eaf903 | ||
|
|
107e3e7630 | ||
|
|
314c0bb34b | ||
|
|
89a097faa8 | ||
|
|
df01582996 | ||
|
|
8ff56b5bb8 | ||
|
|
2f8f139f7c | ||
|
|
b2280c85b7 | ||
|
|
002b8af94a | ||
|
|
99b274acbc | ||
|
|
d4ececae20 | ||
|
|
ff2071c1da | ||
|
|
189b7b9c5f | ||
|
|
a878254662 | ||
|
|
3566542d0e | ||
|
|
ca84ab1a94 | ||
|
|
d84f88b50f | ||
|
|
e3b69b8ae2 | ||
|
|
91a7ffaad2 | ||
|
|
16fadd2441 | ||
|
|
cff7df164f | ||
|
|
de38fa882c | ||
|
|
bd703b8ff2 | ||
|
|
72df27be44 | ||
|
|
f3e23b1128 | ||
|
|
19f7220c0b | ||
|
|
3249572dc1 | ||
|
|
ba09139bd7 | ||
|
|
90bf158e9f | ||
|
|
d1539e6c07 | ||
|
|
c893e3c462 | ||
|
|
b56e73a02a | ||
|
|
40f5c5b987 | ||
|
|
d48517b29d | ||
|
|
3b472f786e | ||
|
|
f3743fc7f9 | ||
|
|
56ed9019a6 | ||
|
|
3e0bd65135 |
16
.github/mihomo.service
vendored
16
.github/mihomo.service
vendored
@@ -1,17 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 2s
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
|
||||
17
.github/mihomo@.service
vendored
Normal file
17
.github/mihomo@.service
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
16
.github/workflows/Delete.yml
vendored
16
.github/workflows/Delete.yml
vendored
@@ -1,16 +0,0 @@
|
||||
name: Delete old workflow runs
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 1 * *'
|
||||
# Run monthly, at 00:00 on the 1st day of month.
|
||||
|
||||
jobs:
|
||||
del_runs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Delete workflow runs
|
||||
uses: GitRML/delete-workflow-runs@main
|
||||
with:
|
||||
token: ${{ secrets.AUTH_PAT }}
|
||||
repository: ${{ github.repository }}
|
||||
retain_days: 30
|
||||
298
.github/workflows/build.yml
vendored
298
.github/workflows/build.yml
vendored
@@ -1,6 +1,10 @@
|
||||
name: Build
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Tag version to release"
|
||||
required: true
|
||||
push:
|
||||
paths-ignore:
|
||||
- "docs/**"
|
||||
@@ -13,9 +17,8 @@ on:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- Alpha
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
group: "${{ github.workflow }}-${{ github.ref }}"
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
@@ -34,11 +37,13 @@ jobs:
|
||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible, test: test }
|
||||
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: linux, goarch: arm64, output: arm64 }
|
||||
- { goos: linux, goarch: arm, goarm: '5', output: armv5 }
|
||||
- { goos: linux, goarch: arm, goarm: '6', output: armv6 }
|
||||
- { goos: linux, goarch: arm, goarm: '7', output: armv7 }
|
||||
- { goos: linux, goarch: mips, mips: hardfloat, output: mips-hardfloat }
|
||||
- { goos: linux, goarch: mips, mips: softfloat, output: mips-softfloat }
|
||||
- { goos: linux, goarch: mipsle, mips: hardfloat, output: mipsle-hardfloat }
|
||||
- { goos: linux, goarch: mipsle, mips: softfloat, output: mipsle-softfloat }
|
||||
- { goos: linux, goarch: mips, gomips: hardfloat, output: mips-hardfloat }
|
||||
- { goos: linux, goarch: mips, gomips: softfloat, output: mips-softfloat }
|
||||
- { goos: linux, goarch: mipsle, gomips: hardfloat, output: mipsle-hardfloat }
|
||||
- { goos: linux, goarch: mipsle, gomips: softfloat, output: mipsle-softfloat }
|
||||
- { goos: linux, goarch: mips64, output: mips64 }
|
||||
- { goos: linux, goarch: mips64le, output: mips64le }
|
||||
- { goos: linux, goarch: loong64, output: loong64-abi1, abi: '1' }
|
||||
@@ -49,7 +54,6 @@ jobs:
|
||||
- { goos: windows, goarch: '386', output: '386' }
|
||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
|
||||
- { goos: windows, goarch: arm, goarm: '7', output: armv7 }
|
||||
- { goos: windows, goarch: arm64, output: arm64 }
|
||||
|
||||
- { goos: freebsd, goarch: '386', output: '386' }
|
||||
@@ -62,16 +66,45 @@ jobs:
|
||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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' }
|
||||
|
||||
# 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 }
|
||||
@@ -81,39 +114,93 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
|
||||
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Set up Go
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
|
||||
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.jobs.goversion }}
|
||||
|
||||
- name: Set up Go1.21 loongarch abi1
|
||||
- name: Set up Go1.23 loongarch abi1
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.21.5.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.23.0/go1.23.0.linux-amd64-abi1.tar.gz
|
||||
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Set up Go1.21 loongarch abi2
|
||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
|
||||
# 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
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
|
||||
# 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.24 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
||||
run: |
|
||||
wget -q https://github.com/xishang0128/loongarch64-golang/releases/download/1.21.5/go1.21.5.linux-amd64-abi2.tar.gz
|
||||
sudo tar zxf go1.21.5.linux-amd64-abi2.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
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
|
||||
curl https://github.com/MetaCubeX/go/commit/979d6d8bab3823ff572ace26767fd2ce3cf351ae.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ac3e93c061779dfefc0dd13a5b6e6f764a25621e.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.23.x
|
||||
# that means after golang1.24 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||
# 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.23 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
|
||||
run: |
|
||||
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
|
||||
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.22.x
|
||||
# that means after golang1.23 release it must be changed
|
||||
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||
# 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.22 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.22' }}
|
||||
run: |
|
||||
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
|
||||
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
- name: Revert Golang1.21 commit for Windows7/8
|
||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.21' }}
|
||||
run: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='Alpha'}}
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
run: echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set variables
|
||||
if: ${{github.ref_name=='' || github.ref_type=='tag'}}
|
||||
run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_name == 'Alpha' }}
|
||||
run: echo "VERSION=alpha-$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Set Time Variable
|
||||
@@ -121,13 +208,14 @@ jobs:
|
||||
echo "BUILDTIME=$(date)" >> $GITHUB_ENV
|
||||
echo "CGO_ENABLED=0" >> $GITHUB_ENV
|
||||
echo "BUILDTAG=-extldflags --static" >> $GITHUB_ENV
|
||||
echo "GOTOOLCHAIN=local" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup NDK
|
||||
if: ${{ matrix.jobs.goos == 'android' }}
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r26c
|
||||
ndk-version: r28-beta1
|
||||
|
||||
- name: Set NDK path
|
||||
if: ${{ matrix.jobs.goos == 'android' }}
|
||||
@@ -140,8 +228,10 @@ jobs:
|
||||
if: ${{ matrix.jobs.test == 'test' }}
|
||||
run: |
|
||||
go test ./...
|
||||
echo "---test with_gvisor---"
|
||||
go test ./... -tags "with_gvisor" -count=1
|
||||
|
||||
- name: Update UA
|
||||
- name: Update CA
|
||||
run: |
|
||||
sudo apt-get install ca-certificates
|
||||
sudo update-ca-certificates
|
||||
@@ -152,10 +242,10 @@ jobs:
|
||||
GOOS: ${{matrix.jobs.goos}}
|
||||
GOARCH: ${{matrix.jobs.goarch}}
|
||||
GOAMD64: ${{matrix.jobs.goamd64}}
|
||||
GOARM: ${{matrix.jobs.arm}}
|
||||
GOMIPS: ${{matrix.jobs.mips}}
|
||||
GOARM: ${{matrix.jobs.goarm}}
|
||||
GOMIPS: ${{matrix.jobs.gomips}}
|
||||
run: |
|
||||
echo $CGO_ENABLED
|
||||
go env
|
||||
go build -v -tags "with_gvisor" -trimpath -ldflags "${BUILDTAG} -X 'github.com/metacubex/mihomo/constant.Version=${VERSION}' -X 'github.com/metacubex/mihomo/constant.BuildTime=${BUILDTIME}' -w -s -buildid="
|
||||
if [ "${{matrix.jobs.goos}}" = "windows" ]; then
|
||||
cp mihomo.exe mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}.exe
|
||||
@@ -172,27 +262,39 @@ jobs:
|
||||
sudo apt-get install dpkg
|
||||
if [ "${{matrix.jobs.abi}}" = "1" ]; then
|
||||
ARCH=loongarch64
|
||||
elif [ "${{matrix.jobs.goarm}}" = "7" ]; then
|
||||
ARCH=armhf
|
||||
elif [ "${{matrix.jobs.goarch}}" = "arm" ]; then
|
||||
ARCH=armel
|
||||
else
|
||||
ARCH=${{matrix.jobs.goarch}}
|
||||
fi
|
||||
PackageVersion=$(curl -s "https://api.github.com/repos/MetaCubeX/mihomo/releases/latest" | grep -o '"tag_name": "[^"]*' | grep -o '[^"]*$' | sed 's/v//g' )
|
||||
if [ $(git branch | awk -F ' ' '{print $2}') = "Alpha" ]; then
|
||||
PackageVersion="$(echo "${PackageVersion}" | awk -F '.' '{$NF = $NF + 1; print}' OFS='.')-${VERSION}"
|
||||
fi
|
||||
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
|
||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system
|
||||
|
||||
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
|
||||
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/
|
||||
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
|
||||
cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
|
||||
cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
|
||||
mixed-port: 7890
|
||||
external-controller: 127.0.0.1:9090
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
|
||||
/etc/mihomo/config.yaml
|
||||
EOF
|
||||
|
||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
||||
Package: mihomo
|
||||
Version: 1.18.2-${VERSION}
|
||||
Version: ${PackageVersion}
|
||||
Section:
|
||||
Priority: extra
|
||||
Architecture: ${ARCH}
|
||||
@@ -231,7 +333,7 @@ jobs:
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}
|
||||
name: "${{ matrix.jobs.goos }}-${{ matrix.jobs.output }}"
|
||||
path: |
|
||||
mihomo*.gz
|
||||
mihomo*.deb
|
||||
@@ -241,7 +343,7 @@ jobs:
|
||||
|
||||
Upload-Prerelease:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
|
||||
if: ${{ github.event_name != 'workflow_dispatch' && github.ref_type == 'branch' && !startsWith(github.event_name, 'pull_request') }}
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -292,44 +394,62 @@ jobs:
|
||||
|
||||
Upload-Release:
|
||||
permissions: write-all
|
||||
if: ${{ github.ref_type=='tag' }}
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: Meta
|
||||
fetch-depth: '0'
|
||||
fetch-tags: 'true'
|
||||
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD^)" >> $GITHUB_ENV
|
||||
- name: Get tags
|
||||
run: |
|
||||
echo "CURRENTVERSION=${{ github.event.inputs.version }}" >> $GITHUB_ENV
|
||||
git fetch --tags
|
||||
echo "PREVERSION=$(git describe --tags --abbrev=0 HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
- name: Force push Alpha branch to Meta
|
||||
run: |
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git fetch origin Alpha:Alpha
|
||||
git push origin Alpha:Meta --force
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: bin/
|
||||
merge-multiple: true
|
||||
- name: Tag the commit on Alpha
|
||||
run: |
|
||||
git checkout Alpha
|
||||
git tag ${{ github.event.inputs.version }}
|
||||
git push origin ${{ github.event.inputs.version }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
working-directory: bin
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: bin/*
|
||||
generate_release_notes: true
|
||||
body_path: release.md
|
||||
- name: Generate release notes
|
||||
run: |
|
||||
cp ./.github/genReleaseNote.sh ./
|
||||
bash ./genReleaseNote.sh -v ${PREVERSION}...${CURRENTVERSION}
|
||||
rm ./genReleaseNote.sh
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: bin/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Display structure of downloaded files
|
||||
run: ls -R
|
||||
working-directory: bin
|
||||
|
||||
- name: Upload Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: ${{ success() }}
|
||||
with:
|
||||
tag_name: ${{ github.event.inputs.version }}
|
||||
files: bin/*
|
||||
body_path: release.md
|
||||
|
||||
Docker:
|
||||
if: ${{ !startsWith(github.event_name, 'pull_request') }}
|
||||
@@ -358,20 +478,35 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
id: meta_alpha
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ github.repository }}
|
||||
|
||||
images: '${{ env.REGISTRY }}/${{ github.repository }}'
|
||||
|
||||
# Extract metadata (tags, labels) for Docker
|
||||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
id: meta_release
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: '${{ env.REGISTRY }}/${{ github.repository }}'
|
||||
tags: |
|
||||
${{ github.event.inputs.version }}
|
||||
flavor: |
|
||||
latest=true
|
||||
labels: org.opencontainers.image.version=${{ github.event.inputs.version }}
|
||||
|
||||
- name: Show files
|
||||
run: |
|
||||
ls .
|
||||
ls bin/
|
||||
|
||||
|
||||
- name: login to docker REGISTRY
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -382,7 +517,7 @@ jobs:
|
||||
# Build and push Docker image with Buildx (don't push on PR)
|
||||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
@@ -393,5 +528,20 @@ jobs:
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
tags: ${{ steps.meta_alpha.outputs.tags }}
|
||||
labels: ${{ steps.meta_alpha.outputs.labels }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version != '' }}
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: |
|
||||
linux/386
|
||||
linux/amd64
|
||||
linux/arm64
|
||||
linux/arm/v7
|
||||
tags: ${{ steps.meta_release.outputs.tags }}
|
||||
labels: ${{ steps.meta_release.outputs.labels }}
|
||||
@@ -13,7 +13,7 @@ WORKDIR /mihomo
|
||||
COPY bin/ bin/
|
||||
RUN FILE_NAME=`sh file-name.sh` && echo $FILE_NAME && \
|
||||
FILE_NAME=`ls bin/ | egrep "$FILE_NAME.gz"|awk NR==1` && echo $FILE_NAME && \
|
||||
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && echo "$FILE_NAME" > /mihomo-config/test
|
||||
mv bin/$FILE_NAME mihomo.gz && gzip -d mihomo.gz && chmod +x mihomo && echo "$FILE_NAME" > /mihomo-config/test
|
||||
FROM alpine:latest
|
||||
LABEL org.opencontainers.image.source="https://github.com/MetaCubeX/mihomo"
|
||||
|
||||
@@ -23,5 +23,4 @@ VOLUME ["/root/.config/mihomo/"]
|
||||
|
||||
COPY --from=builder /mihomo-config/ /root/.config/mihomo/
|
||||
COPY --from=builder /mihomo/mihomo /mihomo
|
||||
RUN chmod +x /mihomo
|
||||
ENTRYPOINT [ "/mihomo" ]
|
||||
|
||||
4
Makefile
4
Makefile
@@ -163,7 +163,3 @@ clean:
|
||||
CLANG ?= clang-14
|
||||
CFLAGS := -O2 -g -Wall -Werror $(CFLAGS)
|
||||
|
||||
ebpf: export BPF_CLANG := $(CLANG)
|
||||
ebpf: export BPF_CFLAGS := $(CFLAGS)
|
||||
ebpf:
|
||||
cd component/ebpf/ && go generate ./...
|
||||
|
||||
@@ -98,4 +98,4 @@ API.
|
||||
|
||||
This software is released under the GPL-3.0 license.
|
||||
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2FMetaCubeX%2Fmihomo?ref=badge_large)
|
||||
**In addition, any downstream projects not affiliated with `MetaCubeX` shall not contain the word `mihomo` in their names.**
|
||||
@@ -2,6 +2,7 @@ package adapter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -9,13 +10,16 @@ import (
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/queue"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
@@ -37,6 +41,11 @@ type Proxy struct {
|
||||
extra *xsync.MapOf[string, *internalProxyState]
|
||||
}
|
||||
|
||||
// Adapter implements C.Proxy
|
||||
func (p *Proxy) Adapter() C.ProxyAdapter {
|
||||
return p.ProxyAdapter
|
||||
}
|
||||
|
||||
// AliveForTestUrl implements C.Proxy
|
||||
func (p *Proxy) AliveForTestUrl(url string) bool {
|
||||
if state, ok := p.extra.Load(url); ok {
|
||||
@@ -154,8 +163,17 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
||||
mapping["alive"] = p.alive.Load()
|
||||
mapping["name"] = p.Name()
|
||||
mapping["udp"] = p.SupportUDP()
|
||||
mapping["xudp"] = p.SupportXUDP()
|
||||
mapping["tfo"] = p.SupportTFO()
|
||||
mapping["uot"] = p.SupportUOT()
|
||||
|
||||
proxyInfo := p.ProxyInfo()
|
||||
mapping["xudp"] = proxyInfo.XUDP
|
||||
mapping["tfo"] = proxyInfo.TFO
|
||||
mapping["mptcp"] = proxyInfo.MPTCP
|
||||
mapping["smux"] = proxyInfo.SMUX
|
||||
mapping["interface"] = proxyInfo.Interface
|
||||
mapping["dialer-proxy"] = proxyInfo.DialerProxy
|
||||
mapping["routing-mark"] = proxyInfo.RoutingMark
|
||||
|
||||
return json.Marshal(mapping)
|
||||
}
|
||||
|
||||
@@ -230,6 +248,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||
}
|
||||
|
||||
client := http.Client{
|
||||
@@ -252,10 +271,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||
|
||||
if unifiedDelay {
|
||||
second := time.Now()
|
||||
resp, err = client.Do(req)
|
||||
if err == nil {
|
||||
var ignoredErr error
|
||||
var secondResp *http.Response
|
||||
secondResp, ignoredErr = client.Do(req)
|
||||
if ignoredErr == nil {
|
||||
resp = secondResp
|
||||
_ = resp.Body.Close()
|
||||
start = second
|
||||
} else {
|
||||
if strings.HasPrefix(url, "http://") {
|
||||
log.Errorln("%s failed to get the second response from %s: %v", p.Name(), url, ignoredErr)
|
||||
log.Warnln("It is recommended to use HTTPS for provider.health-check.url and group.url to ensure better reliability. Due to some proxy providers hijacking test addresses and not being compatible with repeated HEAD requests, using HTTP may result in failed tests.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ func WithDstAddr(addr net.Addr) Addition {
|
||||
func WithSrcAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
if err := m.SetRemoteAddr(addr); err == nil {
|
||||
metadata.SrcIP = m.DstIP
|
||||
metadata.SrcPort = m.DstPort
|
||||
}
|
||||
@@ -57,7 +57,7 @@ func WithSrcAddr(addr net.Addr) Addition {
|
||||
func WithInAddr(addr net.Addr) Addition {
|
||||
return func(metadata *C.Metadata) {
|
||||
m := C.Metadata{}
|
||||
if err := m.SetRemoteAddr(addr);err ==nil{
|
||||
if err := m.SetRemoteAddr(addr); err == nil {
|
||||
metadata.InIP = m.DstIP
|
||||
metadata.InPort = m.DstPort
|
||||
}
|
||||
@@ -69,3 +69,5 @@ func WithDSCP(dscp uint8) Addition {
|
||||
metadata.DSCP = dscp
|
||||
}
|
||||
}
|
||||
|
||||
func Placeholder(metadata *C.Metadata) {}
|
||||
|
||||
@@ -34,12 +34,5 @@ func SkipAuthRemoteAddress(addr string) bool {
|
||||
}
|
||||
|
||||
func skipAuth(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range skipAuthPrefixes {
|
||||
if prefix.Contains(addr.Unmap()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(skipAuthPrefixes, addr)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func NewHTTP(target socks5.Addr, srcConn net.Conn, conn net.Conn, additions ...A
|
||||
metadata.Type = C.HTTP
|
||||
metadata.RawSrcAddr = srcConn.RemoteAddr()
|
||||
metadata.RawDstAddr = srcConn.LocalAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, WithSrcAddr(srcConn.RemoteAddr()), WithInAddr(srcConn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
func NewHTTPS(request *http.Request, conn net.Conn, additions ...Addition) (net.Conn, *C.Metadata) {
|
||||
metadata := parseHTTPAddr(request)
|
||||
metadata.Type = C.HTTPS
|
||||
metadata.RawSrcAddr = conn.RemoteAddr()
|
||||
metadata.RawDstAddr = conn.LocalAddr()
|
||||
ApplyAdditions(metadata, WithSrcAddr(conn.RemoteAddr()), WithInAddr(conn.LocalAddr()))
|
||||
ApplyAdditions(metadata, additions...)
|
||||
return conn, metadata
|
||||
|
||||
@@ -31,27 +31,17 @@ func IsRemoteAddrDisAllowed(addr net.Addr) bool {
|
||||
if err := m.SetRemoteAddr(addr); err != nil {
|
||||
return false
|
||||
}
|
||||
return isAllowed(m.AddrPort().Addr().Unmap()) && !isDisAllowed(m.AddrPort().Addr().Unmap())
|
||||
ipAddr := m.AddrPort().Addr()
|
||||
if ipAddr.IsValid() {
|
||||
return isAllowed(ipAddr) && !isDisAllowed(ipAddr)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isAllowed(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range lanAllowedIPs {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(lanAllowedIPs, addr)
|
||||
}
|
||||
|
||||
func isDisAllowed(addr netip.Addr) bool {
|
||||
if addr.IsValid() {
|
||||
for _, prefix := range lanDisAllowedIPs {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return prefixesContains(lanDisAllowedIPs, addr)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,12 @@ package inbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
@@ -11,20 +16,91 @@ var (
|
||||
lc = tfo.ListenConfig{
|
||||
DisableTFO: true,
|
||||
}
|
||||
mutex sync.RWMutex
|
||||
)
|
||||
|
||||
func SetTfo(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
lc.DisableTFO = !open
|
||||
}
|
||||
|
||||
func Tfo() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return !lc.DisableTFO
|
||||
}
|
||||
|
||||
func SetMPTCP(open bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
setMultiPathTCP(&lc.ListenConfig, open)
|
||||
}
|
||||
|
||||
func MPTCP() bool {
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return getMultiPathTCP(&lc.ListenConfig)
|
||||
}
|
||||
|
||||
func preResolve(network, address string) (string, error) {
|
||||
switch network { // like net.Resolver.internetAddrList but filter domain to avoid call net.Resolver.lookupIPAddr
|
||||
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6":
|
||||
if host, port, err := net.SplitHostPort(address); err == nil {
|
||||
switch host {
|
||||
case "localhost":
|
||||
switch network {
|
||||
case "tcp6", "udp6", "ip6":
|
||||
address = net.JoinHostPort("::1", port)
|
||||
default:
|
||||
address = net.JoinHostPort("127.0.0.1", port)
|
||||
}
|
||||
case "": // internetAddrList can handle this special case
|
||||
break
|
||||
default:
|
||||
if _, err := netip.ParseAddr(host); err != nil { // not ip
|
||||
return "", fmt.Errorf("invalid network address: %s", address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return address, nil
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
address, err := preResolve(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return lc.Listen(ctx, network, address)
|
||||
}
|
||||
|
||||
func Listen(network, address string) (net.Listener, error) {
|
||||
return ListenContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func ListenPacketContext(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
address, err := preResolve(network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mutex.RLock()
|
||||
defer mutex.RUnlock()
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
||||
|
||||
func ListenPacket(network, address string) (net.PacketConn, error) {
|
||||
return ListenPacketContext(context.Background(), network, address)
|
||||
}
|
||||
|
||||
func init() {
|
||||
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
keepalive.SetNetListenConfig(&lc.ListenConfig)
|
||||
})
|
||||
}
|
||||
|
||||
14
adapter/inbound/listen_notwindows.go
Normal file
14
adapter/inbound/listen_notwindows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
//go:build !windows
|
||||
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
const SupportNamedPipe = false
|
||||
|
||||
func ListenNamedPipe(path string) (net.Listener, error) {
|
||||
return nil, os.ErrInvalid
|
||||
}
|
||||
32
adapter/inbound/listen_windows.go
Normal file
32
adapter/inbound/listen_windows.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/metacubex/wireguard-go/ipc/namedpipe"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const SupportNamedPipe = true
|
||||
|
||||
// windowsSDDL is the Security Descriptor set on the namedpipe.
|
||||
// It provides read/write access to all users and the local system.
|
||||
const windowsSDDL = "D:PAI(A;OICI;GWGR;;;BU)(A;OICI;GWGR;;;SY)"
|
||||
|
||||
func ListenNamedPipe(path string) (net.Listener, error) {
|
||||
sddl := os.Getenv("LISTEN_NAMEDPIPE_SDDL")
|
||||
if sddl == "" {
|
||||
sddl = windowsSDDL
|
||||
}
|
||||
securityDescriptor, err := windows.SecurityDescriptorFromString(sddl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namedpipeLC := namedpipe.ListenConfig{
|
||||
SecurityDescriptor: securityDescriptor,
|
||||
InputBufferSize: 256 * 1024,
|
||||
OutputBufferSize: 256 * 1024,
|
||||
}
|
||||
return namedpipeLC.Listen(path)
|
||||
}
|
||||
@@ -8,3 +8,7 @@ const multipathTCPAvailable = false
|
||||
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
}
|
||||
|
||||
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,3 +9,7 @@ const multipathTCPAvailable = true
|
||||
func setMultiPathTCP(listenConfig *net.ListenConfig, open bool) {
|
||||
listenConfig.SetMultipathTCP(open)
|
||||
}
|
||||
|
||||
func getMultiPathTCP(listenConfig *net.ListenConfig) bool {
|
||||
return listenConfig.MultipathTCP()
|
||||
}
|
||||
|
||||
@@ -61,3 +61,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
func prefixesContains(prefixes []netip.Prefix, addr netip.Addr) bool {
|
||||
if len(prefixes) == 0 {
|
||||
return false
|
||||
}
|
||||
if !addr.IsValid() {
|
||||
return false
|
||||
}
|
||||
addr = addr.Unmap().WithZone("") // netip.Prefix.Contains returns false if ip has an IPv6 zone
|
||||
for _, prefix := range prefixes {
|
||||
if prefix.Contains(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
140
adapter/outbound/anytls.go
Normal file
140
adapter/outbound/anytls.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
type AnyTLS struct {
|
||||
*Base
|
||||
client *anytls.Client
|
||||
dialer proxydialer.SingDialer
|
||||
option *AnyTLSOption
|
||||
}
|
||||
|
||||
type AnyTLSOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
MinIdleSession int `proxy:"min-idle-session,omitempty"`
|
||||
}
|
||||
|
||||
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := t.client.CreateProxy(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(CN.NewRefConn(c, t), t), nil
|
||||
}
|
||||
|
||||
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
// create tcp
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create uot on tcp
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), t), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (t *AnyTLS) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
tOption := anytls.ClientConfig{
|
||||
Password: option.Password,
|
||||
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
||||
Dialer: singDialer,
|
||||
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
|
||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||
MinIdleSession: option.MinIdleSession,
|
||||
}
|
||||
tlsConfig := &vmess.TLSConfig{
|
||||
Host: option.SNI,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
}
|
||||
if tlsConfig.Host == "" {
|
||||
tlsConfig.Host = option.Server
|
||||
}
|
||||
if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
|
||||
tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
outbound := &AnyTLS{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.AnyTLS,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
client: anytls.NewClient(context.TODO(), tOption),
|
||||
option: &option,
|
||||
dialer: singDialer,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
|
||||
_ = o.client.Close()
|
||||
})
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
@@ -85,14 +85,15 @@ func (b *Base) SupportUDP() bool {
|
||||
return b.udp
|
||||
}
|
||||
|
||||
// SupportXUDP implements C.ProxyAdapter
|
||||
func (b *Base) SupportXUDP() bool {
|
||||
return b.xudp
|
||||
}
|
||||
|
||||
// SupportTFO implements C.ProxyAdapter
|
||||
func (b *Base) SupportTFO() bool {
|
||||
return b.tfo
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (b *Base) ProxyInfo() (info C.ProxyInfo) {
|
||||
info.XUDP = b.xudp
|
||||
info.TFO = b.tfo
|
||||
info.MPTCP = b.mpTcp
|
||||
info.SMUX = false
|
||||
info.Interface = b.iface
|
||||
info.RoutingMark = b.rmark
|
||||
return
|
||||
}
|
||||
|
||||
// IsL3Protocol implements C.ProxyAdapter
|
||||
@@ -152,11 +153,11 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||
}
|
||||
|
||||
type BasicOption struct {
|
||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
||||
MPTCP bool `proxy:"mptcp,omitempty" group:"mptcp,omitempty"`
|
||||
TFO bool `proxy:"tfo,omitempty"`
|
||||
MPTCP bool `proxy:"mptcp,omitempty"`
|
||||
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
|
||||
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
|
||||
IPVersion string `proxy:"ip-version,omitempty"`
|
||||
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
|
||||
}
|
||||
|
||||
|
||||
@@ -3,18 +3,15 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/loopback"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
type Direct struct {
|
||||
*Base
|
||||
loopBack *loopBackDetector
|
||||
loopBack *loopback.Detector
|
||||
}
|
||||
|
||||
type DirectOption struct {
|
||||
@@ -24,38 +21,41 @@ type DirectOption struct {
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
if d.loopBack.CheckConn(metadata.SourceAddrPort()) {
|
||||
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
|
||||
if err := d.loopBack.CheckConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, dialer.WithResolver(resolver.DefaultResolver))
|
||||
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver))
|
||||
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return d.loopBack.NewConn(NewConn(c, d)), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
if d.loopBack.CheckPacketConn(metadata.SourceAddrPort()) {
|
||||
return nil, fmt.Errorf("reject loopback connection to: %s", metadata.RemoteAddress())
|
||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", netip.AddrPortFrom(metadata.DstIP, metadata.DstPort))
|
||||
pc, err := dialer.NewDialer(d.Base.DialOptions(opts...)...).ListenPacket(ctx, "udp", "", metadata.AddrPort())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.loopBack.NewPacketConn(newPacketConn(pc, d)), nil
|
||||
}
|
||||
|
||||
func (d *Direct) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
return true // tell DNSDialer don't send domain to DialContext, avoid lookback to DefaultResolver
|
||||
}
|
||||
|
||||
func NewDirectWithOption(option DirectOption) *Direct {
|
||||
return &Direct{
|
||||
Base: &Base{
|
||||
@@ -68,7 +68,7 @@ func NewDirectWithOption(option DirectOption) *Direct {
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func NewDirect() *Direct {
|
||||
udp: true,
|
||||
prefer: C.DualStack,
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +92,6 @@ func NewCompatible() *Direct {
|
||||
udp: true,
|
||||
prefer: C.DualStack,
|
||||
},
|
||||
loopBack: newLoopBackDetector(),
|
||||
loopBack: loopback.NewDetector(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/callback"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
type loopBackDetector struct {
|
||||
connMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
packetConnMap *xsync.MapOf[netip.AddrPort, struct{}]
|
||||
}
|
||||
|
||||
func newLoopBackDetector() *loopBackDetector {
|
||||
return &loopBackDetector{
|
||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
packetConnMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) NewConn(conn C.Conn) C.Conn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
l.connMap.Store(connAddr, struct{}{})
|
||||
return callback.NewCloseCallbackConn(conn, func() {
|
||||
l.connMap.Delete(connAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||
metadata := C.Metadata{}
|
||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||
return conn
|
||||
}
|
||||
connAddr := metadata.AddrPort()
|
||||
if !connAddr.IsValid() {
|
||||
return conn
|
||||
}
|
||||
l.packetConnMap.Store(connAddr, struct{}{})
|
||||
return callback.NewCloseCallbackPacketConn(conn, func() {
|
||||
l.packetConnMap.Delete(connAddr)
|
||||
})
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) CheckConn(connAddr netip.AddrPort) bool {
|
||||
if !connAddr.IsValid() {
|
||||
return false
|
||||
}
|
||||
_, ok := l.connMap.Load(connAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (l *loopBackDetector) CheckPacketConn(connAddr netip.AddrPort) bool {
|
||||
if !connAddr.IsValid() {
|
||||
return false
|
||||
}
|
||||
_, ok := l.packetConnMap.Load(connAddr)
|
||||
return ok
|
||||
}
|
||||
@@ -89,14 +89,14 @@ func (d *dnsPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
|
||||
buf := pool.Get(resolver.SafeDnsPacketSize)
|
||||
put := func() { _ = pool.Put(buf) }
|
||||
copy(buf, p) // avoid p be changed after WriteTo returned
|
||||
|
||||
go func() { // don't block the WriteTo function
|
||||
ctx, cancel := context.WithTimeout(d.ctx, resolver.DefaultDnsRelayTimeout)
|
||||
defer cancel()
|
||||
|
||||
buf, err = resolver.RelayDnsPacket(ctx, buf[:len(p)], buf)
|
||||
if err != nil {
|
||||
put()
|
||||
|
||||
@@ -7,13 +7,11 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -76,7 +74,6 @@ func (h *Http) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -95,6 +92,13 @@ func (h *Http) SupportWithDialer() C.NetWork {
|
||||
return C.TCP
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Http) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||
addr := metadata.RemoteAddress()
|
||||
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"github.com/metacubex/quic-go/congestion"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -43,6 +45,8 @@ type Hysteria struct {
|
||||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
@@ -51,7 +55,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(tcpConn, h), nil
|
||||
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
@@ -59,13 +63,13 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newPacketConn(&hyPacketConn{udpConn}, h), nil
|
||||
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
|
||||
}
|
||||
|
||||
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
||||
return &hyDialerWithContext{
|
||||
ctx: context.Background(),
|
||||
hyDialer: func(network string) (net.PacketConn, error) {
|
||||
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
|
||||
var err error
|
||||
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
|
||||
if len(h.option.DialerProxy) > 0 {
|
||||
@@ -74,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
|
||||
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
|
||||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||
},
|
||||
remoteAddr: func(addr string) (net.Addr, error) {
|
||||
@@ -83,6 +87,13 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Hysteria) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
type HysteriaOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
@@ -127,11 +138,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
||||
}
|
||||
|
||||
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
clientTransport := &transport.ClientTransport{
|
||||
Dialer: &net.Dialer{
|
||||
Timeout: 8 * time.Second,
|
||||
},
|
||||
}
|
||||
clientTransport := &transport.ClientTransport{}
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
ports := option.Ports
|
||||
|
||||
@@ -218,7 +225,7 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hysteria %s create error: %w", addr, err)
|
||||
}
|
||||
return &Hysteria{
|
||||
outbound := &Hysteria{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
@@ -231,7 +238,19 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||
},
|
||||
option: &option,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria)
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeHysteria(h *Hysteria) {
|
||||
if h.client != nil {
|
||||
_ = h.client.Close()
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
type hyPacketConn struct {
|
||||
@@ -268,7 +287,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
||||
}
|
||||
|
||||
type hyDialerWithContext struct {
|
||||
hyDialer func(network string) (net.PacketConn, error)
|
||||
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error)
|
||||
ctx context.Context
|
||||
remoteAddr func(host string) (net.Addr, error)
|
||||
}
|
||||
@@ -278,7 +297,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro
|
||||
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
|
||||
network = dialer.ParseNetwork(network, addrPort.Addr())
|
||||
}
|
||||
return h.hyDialer(network)
|
||||
return h.hyDialer(network, rAddr)
|
||||
}
|
||||
|
||||
func (h *hyDialerWithContext) Context() context.Context {
|
||||
|
||||
@@ -21,8 +21,9 @@ import (
|
||||
|
||||
"github.com/metacubex/sing-quic/hysteria2"
|
||||
|
||||
"github.com/metacubex/quic-go"
|
||||
"github.com/metacubex/randv2"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -38,6 +39,8 @@ type Hysteria2 struct {
|
||||
option *Hysteria2Option
|
||||
client *hysteria2.Client
|
||||
dialer proxydialer.SingDialer
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type Hysteria2Option struct {
|
||||
@@ -60,6 +63,12 @@ type Hysteria2Option struct {
|
||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||
CWND int `proxy:"cwnd,omitempty"`
|
||||
UdpMTU int `proxy:"udp-mtu,omitempty"`
|
||||
|
||||
// quic-go special config
|
||||
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
|
||||
MaxStreamReceiveWindow uint64 `proxy:"max-stream-receive-window,omitempty"`
|
||||
InitialConnectionReceiveWindow uint64 `proxy:"initial-connection-receive-window,omitempty"`
|
||||
MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"`
|
||||
}
|
||||
|
||||
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
@@ -89,6 +98,16 @@ func closeHysteria2(h *Hysteria2) {
|
||||
if h.client != nil {
|
||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (h *Hysteria2) ProxyInfo() C.ProxyInfo {
|
||||
info := h.Base.ProxyInfo()
|
||||
info.DialerProxy = h.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
@@ -133,6 +152,13 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
option.UdpMTU = 1200 - 3
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
InitialStreamReceiveWindow: option.InitialStreamReceiveWindow,
|
||||
MaxStreamReceiveWindow: option.MaxStreamReceiveWindow,
|
||||
InitialConnectionReceiveWindow: option.InitialConnectionReceiveWindow,
|
||||
MaxConnectionReceiveWindow: option.MaxConnectionReceiveWindow,
|
||||
}
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
clientOptions := hysteria2.ClientOptions{
|
||||
@@ -144,6 +170,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
SalamanderPassword: salamanderPassword,
|
||||
Password: option.Password,
|
||||
TLSConfig: tlsConfig,
|
||||
QUICConfig: quicConfig,
|
||||
UDPDisabled: false,
|
||||
CWND: option.CWND,
|
||||
UdpMTU: option.UdpMTU,
|
||||
@@ -165,7 +192,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||
})
|
||||
if len(serverAddress) > 0 {
|
||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
||||
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[fastrand.Intn(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||
}
|
||||
|
||||
if option.HopInterval == 0 {
|
||||
|
||||
38
adapter/outbound/hysteria2_test.go
Normal file
38
adapter/outbound/hysteria2_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteria2GC(t *testing.T) {
|
||||
option := Hysteria2Option{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.HopInterval = 30
|
||||
option.Password = "password"
|
||||
option.Obfs = "salamander"
|
||||
option.ObfsPassword = "password"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria2(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
39
adapter/outbound/hysteria_test.go
Normal file
39
adapter/outbound/hysteria_test.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestHysteriaGC(t *testing.T) {
|
||||
option := HysteriaOption{}
|
||||
option.Server = "127.0.0.1"
|
||||
option.Ports = "200,204,401-429,501-503"
|
||||
option.Protocol = "udp"
|
||||
option.Up = "1Mbps"
|
||||
option.Down = "1Mbps"
|
||||
option.HopInterval = 30
|
||||
option.Obfs = "salamander"
|
||||
option.SNI = "example.com"
|
||||
option.ALPN = []string{"h3"}
|
||||
hy, err := NewHysteria(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
hy.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
hy = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
281
adapter/outbound/mieru.go
Normal file
281
adapter/outbound/mieru.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
mieruclient "github.com/enfein/mieru/v3/apis/client"
|
||||
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Mieru struct {
|
||||
*Base
|
||||
option *MieruOption
|
||||
client mieruclient.Client
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
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"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||
if err := m.ensureClientIsRunning(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr := metadataToMieruNetAddrSpec(metadata)
|
||||
c, err := m.client.DialContext(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial to %s failed: %w", addr, err)
|
||||
}
|
||||
return NewConn(c, m), nil
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (m *Mieru) ProxyInfo() C.ProxyInfo {
|
||||
info := m.Base.ProxyInfo()
|
||||
info.DialerProxy = m.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func (m *Mieru) ensureClientIsRunning(opts ...dialer.Option) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.client.IsRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a dialer and add it to the client config, before starting the client.
|
||||
var dialer C.Dialer = dialer.NewDialer(m.Base.DialOptions(opts...)...)
|
||||
var err error
|
||||
if len(m.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(m.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
config, err := m.client.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Dialer = dialer
|
||||
if err := m.client.Store(config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.client.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start mieru client: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewMieru(option MieruOption) (*Mieru, error) {
|
||||
config, err := buildMieruClientConfig(option)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build mieru client config: %w", err)
|
||||
}
|
||||
c := mieruclient.NewClient()
|
||||
if err := c.Store(config); err != nil {
|
||||
return nil, fmt.Errorf("failed to store mieru client config: %w", err)
|
||||
}
|
||||
// Client is started lazily on the first use.
|
||||
|
||||
var addr string
|
||||
if option.Port != 0 {
|
||||
addr = net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
} else {
|
||||
beginPort, _, _ := beginAndEndPortFromPortRange(option.PortRange)
|
||||
addr = net.JoinHostPort(option.Server, strconv.Itoa(beginPort))
|
||||
}
|
||||
outbound := &Mieru{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
iface: option.Interface,
|
||||
tp: C.Mieru,
|
||||
udp: false,
|
||||
xudp: false,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
client: c,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeMieru)
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeMieru(m *Mieru) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.client != nil && m.client.IsRunning() {
|
||||
m.client.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
||||
if metadata.Host != "" {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
FQDN: metadata.Host,
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
}
|
||||
} else {
|
||||
return mierumodel.NetAddrSpec{
|
||||
AddrSpec: mierumodel.AddrSpec{
|
||||
IP: metadata.DstIP.AsSlice(),
|
||||
Port: int(metadata.DstPort),
|
||||
},
|
||||
Net: "tcp",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, error) {
|
||||
if err := validateMieruOption(option); err != nil {
|
||||
return nil, fmt.Errorf("failed to validate mieru option: %w", err)
|
||||
}
|
||||
|
||||
transportProtocol := mierupb.TransportProtocol_TCP.Enum()
|
||||
var server *mierupb.ServerEndpoint
|
||||
if net.ParseIP(option.Server) != nil {
|
||||
// server is an IP address
|
||||
if option.PortRange != "" {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
IpAddress: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
PortRange: proto.String(option.PortRange),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
IpAddress: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
Port: proto.Int32(int32(option.Port)),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// server is a domain name
|
||||
if option.PortRange != "" {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
DomainName: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
PortRange: proto.String(option.PortRange),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
server = &mierupb.ServerEndpoint{
|
||||
DomainName: proto.String(option.Server),
|
||||
PortBindings: []*mierupb.PortBinding{
|
||||
{
|
||||
Port: proto.Int32(int32(option.Port)),
|
||||
Protocol: transportProtocol,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
config := &mieruclient.ClientConfig{
|
||||
Profile: &mierupb.ClientProfile{
|
||||
ProfileName: proto.String(option.Name),
|
||||
User: &mierupb.User{
|
||||
Name: proto.String(option.UserName),
|
||||
Password: proto.String(option.Password),
|
||||
},
|
||||
Servers: []*mierupb.ServerEndpoint{server},
|
||||
},
|
||||
}
|
||||
if multiplexing, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; ok {
|
||||
config.Profile.Multiplexing = &mierupb.MultiplexingConfig{
|
||||
Level: mierupb.MultiplexingLevel(multiplexing).Enum(),
|
||||
}
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func validateMieruOption(option MieruOption) error {
|
||||
if option.Name == "" {
|
||||
return fmt.Errorf("name is empty")
|
||||
}
|
||||
if option.Server == "" {
|
||||
return fmt.Errorf("server is empty")
|
||||
}
|
||||
if option.Port == 0 && option.PortRange == "" {
|
||||
return fmt.Errorf("either port or port-range must be set")
|
||||
}
|
||||
if option.Port != 0 && option.PortRange != "" {
|
||||
return fmt.Errorf("port and port-range cannot be set at the same time")
|
||||
}
|
||||
if option.Port != 0 && (option.Port < 1 || option.Port > 65535) {
|
||||
return fmt.Errorf("port must be between 1 and 65535")
|
||||
}
|
||||
if option.PortRange != "" {
|
||||
begin, end, err := beginAndEndPortFromPortRange(option.PortRange)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid port-range format")
|
||||
}
|
||||
if begin < 1 || begin > 65535 {
|
||||
return fmt.Errorf("begin port must be between 1 and 65535")
|
||||
}
|
||||
if end < 1 || end > 65535 {
|
||||
return fmt.Errorf("end port must be between 1 and 65535")
|
||||
}
|
||||
if begin > end {
|
||||
return fmt.Errorf("begin port must be less than or equal to end port")
|
||||
}
|
||||
}
|
||||
|
||||
if option.Transport != "TCP" {
|
||||
return fmt.Errorf("transport must be TCP")
|
||||
}
|
||||
if option.UserName == "" {
|
||||
return fmt.Errorf("username is empty")
|
||||
}
|
||||
if option.Password == "" {
|
||||
return fmt.Errorf("password is empty")
|
||||
}
|
||||
if option.Multiplexing != "" {
|
||||
if _, ok := mierupb.MultiplexingLevel_value[option.Multiplexing]; !ok {
|
||||
return fmt.Errorf("invalid multiplexing level: %s", option.Multiplexing)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func beginAndEndPortFromPortRange(portRange string) (int, int, error) {
|
||||
var begin, end int
|
||||
_, err := fmt.Sscanf(portRange, "%d-%d", &begin, &end)
|
||||
return begin, end, err
|
||||
}
|
||||
92
adapter/outbound/mieru_test.go
Normal file
92
adapter/outbound/mieru_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package outbound
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestNewMieru(t *testing.T) {
|
||||
testCases := []struct {
|
||||
option MieruOption
|
||||
wantBaseAddr string
|
||||
}{
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "1.2.3.4",
|
||||
Port: 10000,
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "1.2.3.4:10000",
|
||||
},
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "2001:db8::1",
|
||||
PortRange: "10001-10002",
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "[2001:db8::1]:10001",
|
||||
},
|
||||
{
|
||||
option: MieruOption{
|
||||
Name: "test",
|
||||
Server: "example.com",
|
||||
Port: 10003,
|
||||
Transport: "TCP",
|
||||
UserName: "test",
|
||||
Password: "test",
|
||||
},
|
||||
wantBaseAddr: "example.com:10003",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
mieru, err := NewMieru(testCase.option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if mieru.addr != testCase.wantBaseAddr {
|
||||
t.Errorf("got addr %q, want %q", mieru.addr, testCase.wantBaseAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBeginAndEndPortFromPortRange(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
begin int
|
||||
end int
|
||||
hasErr bool
|
||||
}{
|
||||
{"1-10", 1, 10, false},
|
||||
{"1000-2000", 1000, 2000, false},
|
||||
{"65535-65535", 65535, 65535, false},
|
||||
{"1", 0, 0, true},
|
||||
{"1-", 0, 0, true},
|
||||
{"-10", 0, 0, true},
|
||||
{"a-b", 0, 0, true},
|
||||
{"1-b", 0, 0, true},
|
||||
{"a-10", 0, 0, true},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
begin, end, err := beginAndEndPortFromPortRange(testCase.input)
|
||||
if testCase.hasErr {
|
||||
if err == nil {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) should return an error", testCase.input)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) should not return an error, but got %v", testCase.input, err)
|
||||
}
|
||||
if begin != testCase.begin {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) begin port mismatch, got %d, want %d", testCase.input, begin, testCase.begin)
|
||||
}
|
||||
if end != testCase.end {
|
||||
t.Errorf("beginAndEndPortFromPortRange(%s) end port mismatch, got %d, want %d", testCase.input, end, testCase.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func NewRejectWithOption(option RejectOption) *Reject {
|
||||
return &Reject{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
tp: C.Direct,
|
||||
tp: C.Reject,
|
||||
udp: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -149,7 +149,6 @@ func (ss *ShadowSocks) DialContextWithDialer(ctx context.Context, dialer C.Diale
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -166,12 +165,6 @@ func (ss *ShadowSocks) ListenPacketContext(ctx context.Context, metadata *C.Meta
|
||||
|
||||
// ListenPacketWithDialer implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if ss.option.UDPOverTCP {
|
||||
tcpConn, err := ss.DialContextWithDialer(ctx, dialer, metadata)
|
||||
if err != nil {
|
||||
@@ -179,6 +172,12 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
||||
}
|
||||
return ss.ListenPacketOnStreamConn(ctx, tcpConn, metadata)
|
||||
}
|
||||
if len(ss.option.DialerProxy) > 0 {
|
||||
dialer, err = proxydialer.NewByName(ss.option.DialerProxy, dialer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -197,6 +196,13 @@ func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
|
||||
info := ss.Base.ProxyInfo()
|
||||
info.DialerProxy = ss.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
if ss.option.UDPOverTCP {
|
||||
@@ -230,7 +236,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
Password: option.Password,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
|
||||
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
|
||||
}
|
||||
|
||||
var v2rayOption *v2rayObfs.Option
|
||||
@@ -273,6 +279,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
if opts.TLS {
|
||||
v2rayOption.TLS = true
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.Fingerprint = opts.Fingerprint
|
||||
}
|
||||
} else if option.Plugin == shadowtls.Mode {
|
||||
obfsMode = shadowtls.Mode
|
||||
|
||||
@@ -80,7 +80,6 @@ func (ssr *ShadowSocksR) DialContextWithDialer(ctx context.Context, dialer C.Dia
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ssr.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -123,6 +122,13 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
|
||||
info := ssr.Base.ProxyInfo()
|
||||
info.DialerProxy = ssr.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
// SSR protocol compatibility
|
||||
// https://github.com/metacubex/mihomo/pull/2056
|
||||
@@ -135,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||
password := option.Password
|
||||
coreCiph, err := core.PickCipher(cipher, nil, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
|
||||
return nil, fmt.Errorf("ssr %s cipher: %s initialize error: %w", addr, cipher, err)
|
||||
}
|
||||
var (
|
||||
ivSize int
|
||||
|
||||
@@ -97,6 +97,12 @@ func (s *SingMux) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SingMux) ProxyInfo() C.ProxyInfo {
|
||||
info := s.ProxyAdapter.ProxyInfo()
|
||||
info.SMUX = true
|
||||
return info
|
||||
}
|
||||
|
||||
func closeSingMux(s *SingMux) {
|
||||
_ = s.client.Close()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -94,7 +93,6 @@ func (s *Snell) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", s.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -122,7 +120,6 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
@@ -144,6 +141,13 @@ func (s *Snell) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (s *Snell) ProxyInfo() C.ProxyInfo {
|
||||
info := s.Base.ProxyInfo()
|
||||
info.DialerProxy = s.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSnell(option SnellOption) (*Snell, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
psk := []byte(option.Psk)
|
||||
@@ -208,7 +212,6 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
@@ -82,7 +81,6 @@ func (ss *Socks5) DialContextWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -128,7 +126,6 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
N.TCPKeepAlive(c)
|
||||
var user *socks5.User
|
||||
if ss.user != "" {
|
||||
user = &socks5.User{
|
||||
@@ -174,6 +171,13 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (ss *Socks5) ProxyInfo() C.ProxyInfo {
|
||||
info := ss.Base.ProxyInfo()
|
||||
info.DialerProxy = ss.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
@@ -77,7 +77,6 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -122,6 +121,13 @@ func closeSsh(s *Ssh) {
|
||||
_ = s.client.Close()
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (s *Ssh) ProxyInfo() C.ProxyInfo {
|
||||
info := s.Base.ProxyInfo()
|
||||
info.DialerProxy = s.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewSsh(option SshOption) (*Ssh, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
@@ -180,10 +186,10 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
||||
}
|
||||
|
||||
version := "SSH-2.0-OpenSSH_"
|
||||
if fastrand.Intn(2) == 0 {
|
||||
version += "7." + strconv.Itoa(fastrand.Intn(10))
|
||||
if randv2.IntN(2) == 0 {
|
||||
version += "7." + strconv.Itoa(randv2.IntN(10))
|
||||
} else {
|
||||
version += "8." + strconv.Itoa(fastrand.Intn(9))
|
||||
version += "8." + strconv.Itoa(randv2.IntN(9))
|
||||
}
|
||||
config.ClientVersion = version
|
||||
|
||||
|
||||
@@ -3,18 +3,19 @@ package outbound
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
"github.com/metacubex/mihomo/transport/trojan"
|
||||
)
|
||||
|
||||
@@ -29,6 +30,8 @@ type Trojan struct {
|
||||
transport *gun.TransportWrap
|
||||
|
||||
realityConfig *tlsC.RealityConfig
|
||||
|
||||
ssCipher core.Cipher
|
||||
}
|
||||
|
||||
type TrojanOption struct {
|
||||
@@ -46,9 +49,17 @@ type TrojanOption struct {
|
||||
RealityOpts RealityOptions `proxy:"reality-opts,omitempty"`
|
||||
GrpcOpts GrpcOptions `proxy:"grpc-opts,omitempty"`
|
||||
WSOpts WSOptions `proxy:"ws-opts,omitempty"`
|
||||
SSOpts TrojanSSOption `proxy:"ss-opts,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||
type TrojanSSOption struct {
|
||||
Enabled bool `proxy:"enabled,omitempty"`
|
||||
Method string `proxy:"method,omitempty"`
|
||||
Password string `proxy:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
|
||||
if t.option.Network == "ws" {
|
||||
host, port, _ := net.SplitHostPort(t.addr)
|
||||
@@ -95,6 +106,10 @@ func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
if metadata.NetWork == C.UDP {
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
return c, err
|
||||
@@ -112,6 +127,10 @@ func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
@@ -134,7 +153,6 @@ func (t *Trojan) DialContextWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
@@ -161,6 +179,11 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -187,12 +210,15 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
N.TCPKeepAlive(c)
|
||||
c, err = t.plainStream(ctx, c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -218,6 +244,13 @@ func (t *Trojan) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *Trojan) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
@@ -257,6 +290,20 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
}
|
||||
tOption.Reality = t.realityConfig
|
||||
|
||||
if option.SSOpts.Enabled {
|
||||
if option.SSOpts.Password == "" {
|
||||
return nil, errors.New("empty password")
|
||||
}
|
||||
if option.SSOpts.Method == "" {
|
||||
option.SSOpts.Method = "AES-128-GCM"
|
||||
}
|
||||
ciph, err := core.PickCipher(option.SSOpts.Method, nil, option.SSOpts.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.ssCipher = ciph
|
||||
}
|
||||
|
||||
if option.Network == "grpc" {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
var err error
|
||||
@@ -271,7 +318,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,13 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
|
||||
return
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *Tuic) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
serverName := option.Server
|
||||
|
||||
@@ -55,7 +55,7 @@ func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, err := resolver.ResolveProxyServerHost(ctx, host)
|
||||
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -71,12 +71,12 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
|
||||
var fallback netip.Addr
|
||||
switch prefer {
|
||||
case C.IPv4Only:
|
||||
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
||||
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
case C.IPv6Only:
|
||||
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
||||
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
case C.IPv6Prefer:
|
||||
var ips []netip.Addr
|
||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
if err == nil {
|
||||
for _, addr := range ips {
|
||||
if addr.Is6() {
|
||||
@@ -92,7 +92,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
|
||||
default:
|
||||
// C.IPv4Prefer, C.DualStack and other
|
||||
var ips []netip.Addr
|
||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
if err == nil {
|
||||
for _, addr := range ips {
|
||||
if addr.Is4() {
|
||||
|
||||
@@ -21,7 +21,6 @@ import (
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
@@ -262,7 +261,6 @@ func (v *Vless) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -327,7 +325,6 @@ func (v *Vless) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -381,6 +378,13 @@ func (v *Vless) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (v *Vless) ProxyInfo() C.ProxyInfo {
|
||||
info := v.Base.ProxyInfo()
|
||||
info.DialerProxy = v.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||
var addrType byte
|
||||
var addr []byte
|
||||
@@ -505,17 +509,12 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
var addons *vless.Addons
|
||||
if option.Network != "ws" && len(option.Flow) >= 16 {
|
||||
option.Flow = option.Flow[:16]
|
||||
switch option.Flow {
|
||||
case vless.XRV:
|
||||
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
|
||||
addons = &vless.Addons{
|
||||
Flow: option.Flow,
|
||||
}
|
||||
case vless.XRO, vless.XRD, vless.XRS:
|
||||
log.Fatalln("Legacy XTLS protocol %s is deprecated and no longer supported", option.Flow)
|
||||
default:
|
||||
if option.Flow != vless.XRV {
|
||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||
}
|
||||
addons = &vless.Addons{
|
||||
Flow: option.Flow,
|
||||
}
|
||||
}
|
||||
|
||||
switch option.PacketEncoding {
|
||||
@@ -577,7 +576,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +179,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
tlsOpts := mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
NextProtos: []string{"h2"},
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
@@ -208,6 +209,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||
tlsOpts := &mihomoVMess.TLSConfig{
|
||||
Host: host,
|
||||
SkipCertVerify: v.option.SkipCertVerify,
|
||||
FingerPrint: v.option.Fingerprint,
|
||||
ClientFingerprint: v.option.ClientFingerprint,
|
||||
Reality: v.realityConfig,
|
||||
NextProtos: v.option.ALPN,
|
||||
@@ -310,7 +312,6 @@ func (v *Vmess) DialContextWithDialer(ctx context.Context, dialer C.Dialer, meta
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -371,7 +372,6 @@ func (v *Vmess) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
defer func(c net.Conn) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
@@ -388,6 +388,13 @@ func (v *Vmess) SupportWithDialer() C.NetWork {
|
||||
return C.ALLNet
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
||||
info := v.Base.ProxyInfo()
|
||||
info.DialerProxy = v.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||
@@ -452,6 +459,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
option: &option,
|
||||
}
|
||||
|
||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch option.Network {
|
||||
case "h2":
|
||||
if len(option.HTTP2Opts.Host) == 0 {
|
||||
@@ -471,7 +483,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||
}
|
||||
N.TCPKeepAlive(c)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
@@ -501,11 +512,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
||||
}
|
||||
|
||||
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
@@ -23,24 +24,41 @@ import (
|
||||
"github.com/metacubex/mihomo/dns"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
amnezia "github.com/metacubex/amneziawg-go/device"
|
||||
wireguard "github.com/metacubex/sing-wireguard"
|
||||
"github.com/metacubex/wireguard-go/device"
|
||||
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/debug"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/wireguard-go/device"
|
||||
)
|
||||
|
||||
type wireguardGoDevice interface {
|
||||
Close()
|
||||
IpcSet(uapiConf string) error
|
||||
}
|
||||
|
||||
type WireGuard struct {
|
||||
*Base
|
||||
bind *wireguard.ClientBind
|
||||
device *device.Device
|
||||
device wireguardGoDevice
|
||||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
init func(ctx context.Context) error
|
||||
resolver *dns.Resolver
|
||||
resolver resolver.Resolver
|
||||
refP *refProxyAdapter
|
||||
|
||||
initOk atomic.Bool
|
||||
initMutex sync.Mutex
|
||||
initErr error
|
||||
option WireGuardOption
|
||||
connectAddr M.Socksaddr
|
||||
localPrefixes []netip.Prefix
|
||||
|
||||
serverAddrMap map[M.Socksaddr]netip.AddrPort
|
||||
serverAddrTime atomic.TypedValue[time.Time]
|
||||
serverAddrMutex sync.Mutex
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type WireGuardOption struct {
|
||||
@@ -55,10 +73,14 @@ type WireGuardOption struct {
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
PersistentKeepalive int `proxy:"persistent-keepalive,omitempty"`
|
||||
|
||||
AmneziaWGOption *AmneziaWGOption `proxy:"amnezia-wg-option,omitempty"`
|
||||
|
||||
Peers []WireGuardPeerOption `proxy:"peers,omitempty"`
|
||||
|
||||
RemoteDnsResolve bool `proxy:"remote-dns-resolve,omitempty"`
|
||||
Dns []string `proxy:"dns,omitempty"`
|
||||
|
||||
RefreshServerIPInterval int `proxy:"refresh-server-ip-interval,omitempty"`
|
||||
}
|
||||
|
||||
type WireGuardPeerOption struct {
|
||||
@@ -70,6 +92,18 @@ type WireGuardPeerOption struct {
|
||||
AllowedIPs []string `proxy:"allowed-ips,omitempty"`
|
||||
}
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type wgSingErrorHandler struct {
|
||||
name string
|
||||
}
|
||||
@@ -141,19 +175,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||
|
||||
resolv := func(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
|
||||
if address.Addr.IsValid() {
|
||||
return address.AddrPort(), nil
|
||||
}
|
||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), outbound.prefer)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
|
||||
addrPort := udpAddr.AddrPort()
|
||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
|
||||
}
|
||||
|
||||
var reserved [3]uint8
|
||||
if len(option.Reserved) > 0 {
|
||||
if len(option.Reserved) != 3 {
|
||||
@@ -162,29 +183,28 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
copy(reserved[:], option.Reserved)
|
||||
}
|
||||
var isConnect bool
|
||||
var connectAddr M.Socksaddr
|
||||
if len(option.Peers) < 2 {
|
||||
isConnect = true
|
||||
if len(option.Peers) == 1 {
|
||||
connectAddr = option.Peers[0].Addr()
|
||||
outbound.connectAddr = option.Peers[0].Addr()
|
||||
} else {
|
||||
connectAddr = option.Addr()
|
||||
outbound.connectAddr = option.Addr()
|
||||
}
|
||||
}
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, connectAddr.AddrPort(), reserved)
|
||||
outbound.bind = wireguard.NewClientBind(context.Background(), wgSingErrorHandler{outbound.Name()}, outbound.dialer, isConnect, outbound.connectAddr.AddrPort(), reserved)
|
||||
|
||||
localPrefixes, err := option.Prefixes()
|
||||
var err error
|
||||
outbound.localPrefixes, err = option.Prefixes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var privateKey string
|
||||
{
|
||||
bytes, err := base64.StdEncoding.DecodeString(option.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "decode private key")
|
||||
}
|
||||
privateKey = hex.EncodeToString(bytes)
|
||||
option.PrivateKey = hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
if len(option.Peers) > 0 {
|
||||
@@ -230,124 +250,36 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
option.PreSharedKey = hex.EncodeToString(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
initOk atomic.Bool
|
||||
initMutex sync.Mutex
|
||||
initErr error
|
||||
)
|
||||
|
||||
outbound.init = func(ctx context.Context) error {
|
||||
if initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
initMutex.Lock()
|
||||
defer initMutex.Unlock()
|
||||
// double check like sync.Once
|
||||
if initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
if initErr != nil {
|
||||
return initErr
|
||||
}
|
||||
|
||||
outbound.bind.ResetReservedForEndpoint()
|
||||
ipcConf := "private_key=" + privateKey
|
||||
if len(option.Peers) > 0 {
|
||||
for i, peer := range option.Peers {
|
||||
destination, err := resolv(ctx, peer.Addr())
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return E.Cause(err, "resolve endpoint domain for peer ", i)
|
||||
}
|
||||
ipcConf += "\npublic_key=" + peer.PublicKey
|
||||
ipcConf += "\nendpoint=" + destination.String()
|
||||
if peer.PreSharedKey != "" {
|
||||
ipcConf += "\npreshared_key=" + peer.PreSharedKey
|
||||
}
|
||||
for _, allowedIP := range peer.AllowedIPs {
|
||||
ipcConf += "\nallowed_ip=" + allowedIP
|
||||
}
|
||||
if len(peer.Reserved) > 0 {
|
||||
copy(reserved[:], option.Reserved)
|
||||
outbound.bind.SetReservedForEndpoint(destination, reserved)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ipcConf += "\npublic_key=" + option.PublicKey
|
||||
destination, err := resolv(ctx, connectAddr)
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return E.Cause(err, "resolve endpoint domain")
|
||||
}
|
||||
outbound.bind.SetConnectAddr(destination)
|
||||
ipcConf += "\nendpoint=" + destination.String()
|
||||
if option.PreSharedKey != "" {
|
||||
ipcConf += "\npreshared_key=" + option.PreSharedKey
|
||||
}
|
||||
var has4, has6 bool
|
||||
for _, address := range localPrefixes {
|
||||
if address.Addr().Is4() {
|
||||
has4 = true
|
||||
} else {
|
||||
has6 = true
|
||||
}
|
||||
}
|
||||
if has4 {
|
||||
ipcConf += "\nallowed_ip=0.0.0.0/0"
|
||||
}
|
||||
if has6 {
|
||||
ipcConf += "\nallowed_ip=::/0"
|
||||
}
|
||||
}
|
||||
|
||||
if option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("\npersistent_keepalive_interval=%d", option.PersistentKeepalive)
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", option.Name, ipcConf))
|
||||
}
|
||||
err = outbound.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
initErr = E.Cause(err, "setup wireguard")
|
||||
return initErr
|
||||
}
|
||||
|
||||
err = outbound.tunDevice.Start()
|
||||
if err != nil {
|
||||
initErr = err
|
||||
return initErr
|
||||
}
|
||||
|
||||
initOk.Store(true)
|
||||
return nil
|
||||
}
|
||||
outbound.option = option
|
||||
|
||||
mtu := option.MTU
|
||||
if mtu == 0 {
|
||||
mtu = 1408
|
||||
}
|
||||
if len(localPrefixes) == 0 {
|
||||
if len(outbound.localPrefixes) == 0 {
|
||||
return nil, E.New("missing local address")
|
||||
}
|
||||
outbound.tunDevice, err = wireguard.NewStackDevice(localPrefixes, uint32(mtu))
|
||||
outbound.tunDevice, err = wireguard.NewStackDevice(outbound.localPrefixes, uint32(mtu))
|
||||
if err != nil {
|
||||
return nil, E.Cause(err, "create WireGuard device")
|
||||
}
|
||||
outbound.device = device.NewDevice(context.Background(), outbound.tunDevice, outbound.bind, &device.Logger{
|
||||
logger := &device.Logger{
|
||||
Verbosef: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Debug(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
Errorf: func(format string, args ...interface{}) {
|
||||
log.SingLogger.Error(fmt.Sprintf("[WG](%s) %s", option.Name, fmt.Sprintf(format, args...)))
|
||||
},
|
||||
}, option.Workers)
|
||||
}
|
||||
if option.AmneziaWGOption != nil {
|
||||
outbound.bind.SetParseReserved(false) // AmneziaWG don't need parse reserved
|
||||
outbound.device = amnezia.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
|
||||
} else {
|
||||
outbound.device = device.NewDevice(outbound.tunDevice, outbound.bind, logger, option.Workers)
|
||||
}
|
||||
|
||||
var has6 bool
|
||||
for _, address := range localPrefixes {
|
||||
for _, address := range outbound.localPrefixes {
|
||||
if !address.Addr().Unmap().Is4() {
|
||||
has6 = true
|
||||
break
|
||||
@@ -373,11 +305,196 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.AddrPort, error) {
|
||||
if address.Addr.IsValid() {
|
||||
return address.AddrPort(), nil
|
||||
}
|
||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer)
|
||||
if err != nil {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
// net.ResolveUDPAddr maybe return 4in6 address, so unmap at here
|
||||
addrPort := udpAddr.AddrPort()
|
||||
return netip.AddrPortFrom(addrPort.Addr().Unmap(), addrPort.Port()), nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) init(ctx context.Context) error {
|
||||
err := w.init0(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.updateServerAddr(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) init0(ctx context.Context) error {
|
||||
if w.initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
w.initMutex.Lock()
|
||||
defer w.initMutex.Unlock()
|
||||
// double check like sync.Once
|
||||
if w.initOk.Load() {
|
||||
return nil
|
||||
}
|
||||
if w.initErr != nil {
|
||||
return w.initErr
|
||||
}
|
||||
|
||||
w.bind.ResetReservedForEndpoint()
|
||||
w.serverAddrMap = make(map[M.Socksaddr]netip.AddrPort)
|
||||
ipcConf, err := w.genIpcConf(ctx, false)
|
||||
if err != nil {
|
||||
// !!! do not set initErr here !!!
|
||||
// let us can retry domain resolve in next time
|
||||
return err
|
||||
}
|
||||
|
||||
if debug.Enabled {
|
||||
log.SingLogger.Trace(fmt.Sprintf("[WG](%s) created wireguard ipc conf: \n %s", w.option.Name, ipcConf))
|
||||
}
|
||||
err = w.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
w.initErr = E.Cause(err, "setup wireguard")
|
||||
return w.initErr
|
||||
}
|
||||
w.serverAddrTime.Store(time.Now())
|
||||
|
||||
err = w.tunDevice.Start()
|
||||
if err != nil {
|
||||
w.initErr = err
|
||||
return w.initErr
|
||||
}
|
||||
|
||||
w.initOk.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WireGuard) updateServerAddr(ctx context.Context) {
|
||||
if w.option.RefreshServerIPInterval != 0 && time.Since(w.serverAddrTime.Load()) > time.Second*time.Duration(w.option.RefreshServerIPInterval) {
|
||||
if w.serverAddrMutex.TryLock() {
|
||||
defer w.serverAddrMutex.Unlock()
|
||||
ipcConf, err := w.genIpcConf(ctx, true)
|
||||
if err != nil {
|
||||
log.Warnln("[WG](%s)UpdateServerAddr failed to generate wireguard ipc conf: %s", w.option.Name, err)
|
||||
return
|
||||
}
|
||||
err = w.device.IpcSet(ipcConf)
|
||||
if err != nil {
|
||||
log.Warnln("[WG](%s)UpdateServerAddr failed to update wireguard ipc conf: %s", w.option.Name, err)
|
||||
return
|
||||
}
|
||||
w.serverAddrTime.Store(time.Now())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, error) {
|
||||
ipcConf := ""
|
||||
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 len(w.option.Peers) > 0 {
|
||||
for i, peer := range w.option.Peers {
|
||||
peerAddr := peer.Addr()
|
||||
destination, err := w.resolve(ctx, peerAddr)
|
||||
if err != nil {
|
||||
return "", E.Cause(err, "resolve endpoint domain for peer ", i)
|
||||
}
|
||||
if w.serverAddrMap[peerAddr] != destination {
|
||||
w.serverAddrMap[peerAddr] = destination
|
||||
} else if updateOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(w.option.Peers) == 1 { // must call SetConnectAddr if isConnect == true
|
||||
w.bind.SetConnectAddr(destination)
|
||||
}
|
||||
ipcConf += "public_key=" + peer.PublicKey + "\n"
|
||||
if updateOnly {
|
||||
ipcConf += "update_only=true\n"
|
||||
}
|
||||
ipcConf += "endpoint=" + destination.String() + "\n"
|
||||
if len(peer.Reserved) > 0 {
|
||||
var reserved [3]uint8
|
||||
copy(reserved[:], w.option.Reserved)
|
||||
w.bind.SetReservedForEndpoint(destination, reserved)
|
||||
}
|
||||
if updateOnly {
|
||||
continue
|
||||
}
|
||||
if peer.PreSharedKey != "" {
|
||||
ipcConf += "preshared_key=" + peer.PreSharedKey + "\n"
|
||||
}
|
||||
for _, allowedIP := range peer.AllowedIPs {
|
||||
ipcConf += "allowed_ip=" + allowedIP + "\n"
|
||||
}
|
||||
if w.option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
destination, err := w.resolve(ctx, w.connectAddr)
|
||||
if err != nil {
|
||||
return "", E.Cause(err, "resolve endpoint domain")
|
||||
}
|
||||
if w.serverAddrMap[w.connectAddr] != destination {
|
||||
w.serverAddrMap[w.connectAddr] = destination
|
||||
} else if updateOnly {
|
||||
return "", nil
|
||||
}
|
||||
w.bind.SetConnectAddr(destination) // must call SetConnectAddr if isConnect == true
|
||||
ipcConf += "public_key=" + w.option.PublicKey + "\n"
|
||||
if updateOnly {
|
||||
ipcConf += "update_only=true\n"
|
||||
}
|
||||
ipcConf += "endpoint=" + destination.String() + "\n"
|
||||
if updateOnly {
|
||||
return ipcConf, nil
|
||||
}
|
||||
if w.option.PreSharedKey != "" {
|
||||
ipcConf += "preshared_key=" + w.option.PreSharedKey + "\n"
|
||||
}
|
||||
var has4, has6 bool
|
||||
for _, address := range w.localPrefixes {
|
||||
if address.Addr().Is4() {
|
||||
has4 = true
|
||||
} else {
|
||||
has6 = true
|
||||
}
|
||||
}
|
||||
if has4 {
|
||||
ipcConf += "allowed_ip=0.0.0.0/0\n"
|
||||
}
|
||||
if has6 {
|
||||
ipcConf += "allowed_ip=::/0\n"
|
||||
}
|
||||
|
||||
if w.option.PersistentKeepalive != 0 {
|
||||
ipcConf += fmt.Sprintf("persistent_keepalive_interval=%d\n", w.option.PersistentKeepalive)
|
||||
}
|
||||
}
|
||||
return ipcConf, nil
|
||||
}
|
||||
|
||||
func closeWireGuard(w *WireGuard) {
|
||||
if w.device != nil {
|
||||
w.device.Close()
|
||||
}
|
||||
_ = common.Close(w.tunDevice)
|
||||
if w.closeCh != nil {
|
||||
close(w.closeCh)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
@@ -416,9 +533,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||
if err = w.init(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
@@ -497,18 +611,11 @@ func (r *refProxyAdapter) SupportUDP() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) SupportXUDP() bool {
|
||||
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
|
||||
if r.proxyAdapter != nil {
|
||||
return r.proxyAdapter.SupportXUDP()
|
||||
return r.proxyAdapter.ProxyInfo()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) SupportTFO() bool {
|
||||
if r.proxyAdapter != nil {
|
||||
return r.proxyAdapter.SupportTFO()
|
||||
}
|
||||
return false
|
||||
return C.ProxyInfo{}
|
||||
}
|
||||
|
||||
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
|
||||
|
||||
45
adapter/outbound/wireguard_test.go
Normal file
45
adapter/outbound/wireguard_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
//go:build with_gvisor
|
||||
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestWireGuardGC(t *testing.T) {
|
||||
option := WireGuardOption{}
|
||||
option.Server = "162.159.192.1"
|
||||
option.Port = 2408
|
||||
option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8="
|
||||
option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo="
|
||||
option.Ip = "172.16.0.2"
|
||||
option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437"
|
||||
option.Reserved = []uint8{51, 69, 125}
|
||||
wg, err := NewWireGuard(option)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
closeCh := make(chan struct{})
|
||||
wg.closeCh = closeCh
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
err = wg.init(ctx)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
// must do a small sleep before test GC
|
||||
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
wg = nil
|
||||
runtime.GC()
|
||||
select {
|
||||
case <-closeCh:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
t.Error("timeout not GC")
|
||||
}
|
||||
}
|
||||
@@ -46,9 +46,16 @@ type GroupBaseOption struct {
|
||||
}
|
||||
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
if opt.RoutingMark != 0 {
|
||||
log.Warnln("The group [%s] with routing-mark configuration is deprecated, please set it directly on the proxy instead", opt.Name)
|
||||
}
|
||||
if opt.Interface != "" {
|
||||
log.Warnln("The group [%s] with interface-name configuration is deprecated, please set it directly on the proxy instead", opt.Name)
|
||||
}
|
||||
|
||||
var excludeFilterReg *regexp2.Regexp
|
||||
if opt.excludeFilter != "" {
|
||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, 0)
|
||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if opt.excludeType != "" {
|
||||
@@ -58,7 +65,7 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var filterRegs []*regexp2.Regexp
|
||||
if opt.filter != "" {
|
||||
for _, filter := range strings.Split(opt.filter, "`") {
|
||||
filterReg := regexp2.MustCompile(filter, 0)
|
||||
filterReg := regexp2.MustCompile(filter, regexp2.None)
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
}
|
||||
@@ -126,7 +133,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
@@ -150,7 +157,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
@@ -191,7 +198,7 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
var newProxies []C.Proxy
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := gb.excludeFilterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
}
|
||||
newProxies = append(newProxies, p)
|
||||
|
||||
@@ -204,8 +204,7 @@ func strategyStickySessions(url string) strategyFn {
|
||||
for i := 1; i < maxRetry; i++ {
|
||||
proxy := proxies[nowIdx]
|
||||
if proxy.AliveForTestUrl(url) {
|
||||
if nowIdx != idx {
|
||||
lruCache.Delete(key)
|
||||
if !has || nowIdx != idx {
|
||||
lruCache.Set(key, nowIdx)
|
||||
}
|
||||
|
||||
@@ -215,7 +214,6 @@ func strategyStickySessions(url string) strategyFn {
|
||||
}
|
||||
}
|
||||
|
||||
lruCache.Delete(key)
|
||||
lruCache.Set(key, 0)
|
||||
return proxies[0]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/adapter/provider"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
@@ -67,10 +69,28 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
}
|
||||
|
||||
if groupOption.IncludeAllProviders {
|
||||
groupOption.Use = append(groupOption.Use, AllProviders...)
|
||||
groupOption.Use = AllProviders
|
||||
}
|
||||
if groupOption.IncludeAllProxies {
|
||||
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
|
||||
if groupOption.Filter != "" {
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(groupOption.Filter, "`") {
|
||||
filterReg := regexp2.MustCompile(filter, regexp2.None)
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
for _, p := range AllProxies {
|
||||
for _, filterReg := range filterRegs {
|
||||
if mat, _ := filterReg.MatchString(p); mat {
|
||||
groupOption.Proxies = append(groupOption.Proxies, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
groupOption.Proxies = append(groupOption.Proxies, AllProxies...)
|
||||
}
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
groupOption.Proxies = []string{"COMPATIBLE"}
|
||||
}
|
||||
}
|
||||
|
||||
if len(groupOption.Proxies) == 0 && len(groupOption.Use) == 0 {
|
||||
@@ -88,6 +108,29 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
}
|
||||
groupOption.ExpectedStatus = status
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
PDs, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
// if test URL is empty, use the first health check URL of providers
|
||||
if groupOption.URL == "" {
|
||||
for _, pd := range PDs {
|
||||
if pd.HealthCheckURL() != "" {
|
||||
groupOption.URL = pd.HealthCheckURL()
|
||||
break
|
||||
}
|
||||
}
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
} else {
|
||||
addTestUrlToProviders(PDs, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
}
|
||||
providers = append(providers, PDs...)
|
||||
}
|
||||
|
||||
if len(groupOption.Proxies) != 0 {
|
||||
ps, err := getProxies(proxyMap, groupOption.Proxies)
|
||||
if err != nil {
|
||||
@@ -98,14 +141,15 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, fmt.Errorf("%s: %w", groupName, errDuplicateProvider)
|
||||
}
|
||||
|
||||
// select don't need health check
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
|
||||
// select don't need auto health check
|
||||
if groupOption.Type != "select" && groupOption.Type != "relay" {
|
||||
if groupOption.Interval == 0 {
|
||||
groupOption.Interval = 300
|
||||
}
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
}
|
||||
|
||||
hc := provider.NewHealthCheck(ps, groupOption.URL, uint(groupOption.TestTimeout), uint(groupOption.Interval), groupOption.Lazy, expectedStatus)
|
||||
@@ -115,34 +159,10 @@ func ParseProxyGroup(config map[string]any, proxyMap map[string]C.Proxy, provide
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
providers = append(providers, pd)
|
||||
providers = append([]types.ProxyProvider{pd}, providers...)
|
||||
providersMap[groupName] = pd
|
||||
}
|
||||
|
||||
if len(groupOption.Use) != 0 {
|
||||
list, err := getProviders(providersMap, groupOption.Use)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", groupName, err)
|
||||
}
|
||||
|
||||
if groupOption.URL == "" {
|
||||
for _, p := range list {
|
||||
if p.HealthCheckURL() != "" {
|
||||
groupOption.URL = p.HealthCheckURL()
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if groupOption.URL == "" {
|
||||
groupOption.URL = C.DefaultTestURL
|
||||
}
|
||||
}
|
||||
|
||||
// different proxy groups use different test URL
|
||||
addTestUrlToProviders(list, groupOption.URL, expectedStatus, groupOption.Filter, uint(groupOption.Interval))
|
||||
providers = append(providers, list...)
|
||||
}
|
||||
|
||||
var group C.ProxyAdapter
|
||||
switch groupOption.Type {
|
||||
case "url-test":
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type Relay struct {
|
||||
@@ -149,6 +150,7 @@ func (r *Relay) Addr() string {
|
||||
}
|
||||
|
||||
func NewRelay(option *GroupCommonOption, providers []provider.ProxyProvider) *Relay {
|
||||
log.Warnln("The group [%s] with relay type is deprecated, please using dialer-proxy instead", option.Name)
|
||||
return &Relay{
|
||||
GroupBase: NewGroupBase(GroupBaseOption{
|
||||
outbound.BaseOption{
|
||||
|
||||
@@ -4,3 +4,7 @@ type SelectAble interface {
|
||||
Set(string) error
|
||||
ForceSet(name string)
|
||||
}
|
||||
|
||||
var _ SelectAble = (*Fallback)(nil)
|
||||
var _ SelectAble = (*URLTest)(nil)
|
||||
var _ SelectAble = (*Selector)(nil)
|
||||
|
||||
@@ -141,6 +141,20 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewSsh(*sshOption)
|
||||
case "mieru":
|
||||
mieruOption := &outbound.MieruOption{}
|
||||
err = decoder.Decode(mapping, mieruOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewMieru(*mieruOption)
|
||||
case "anytls":
|
||||
anytlsOption := &outbound.AnyTLSOption{}
|
||||
err = decoder.Decode(mapping, anytlsOption)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
proxy, err = outbound.NewAnyTLS(*anytlsOption)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ type extraOption struct {
|
||||
}
|
||||
|
||||
type HealthCheck struct {
|
||||
ctx context.Context
|
||||
ctxCancel context.CancelFunc
|
||||
url string
|
||||
extra map[string]*extraOption
|
||||
mu sync.Mutex
|
||||
@@ -36,7 +38,6 @@ type HealthCheck struct {
|
||||
lazy bool
|
||||
expectedStatus utils.IntRanges[uint16]
|
||||
lastTouch atomic.TypedValue[time.Time]
|
||||
done chan struct{}
|
||||
singleDo *singledo.Single[struct{}]
|
||||
timeout time.Duration
|
||||
}
|
||||
@@ -59,7 +60,7 @@ func (hc *HealthCheck) process() {
|
||||
} else {
|
||||
log.Debugln("Skip once health check because we are lazy")
|
||||
}
|
||||
case <-hc.done:
|
||||
case <-hc.ctx.Done():
|
||||
ticker.Stop()
|
||||
hc.stop()
|
||||
return
|
||||
@@ -146,7 +147,7 @@ func (hc *HealthCheck) check() {
|
||||
_, _, _ = hc.singleDo.Do(func() (struct{}, error) {
|
||||
id := utils.NewUUIDV4().String()
|
||||
log.Debugln("Start New Health Checking {%s}", id)
|
||||
b, _ := batch.New[bool](context.Background(), batch.WithConcurrencyNum[bool](10))
|
||||
b, _ := batch.New[bool](hc.ctx, batch.WithConcurrencyNum[bool](10))
|
||||
|
||||
// execute default health check
|
||||
option := &extraOption{filters: nil, expectedStatus: hc.expectedStatus}
|
||||
@@ -181,21 +182,21 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||
filters = append(filters, filter)
|
||||
}
|
||||
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), 0)
|
||||
filterReg = regexp2.MustCompile(strings.Join(filters, "|"), regexp2.None)
|
||||
}
|
||||
}
|
||||
|
||||
for _, proxy := range hc.proxies {
|
||||
// skip proxies that do not require health check
|
||||
if filterReg != nil {
|
||||
if match, _ := filterReg.FindStringMatch(proxy.Name()); match == nil {
|
||||
if match, _ := filterReg.MatchString(proxy.Name()); !match {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
p := proxy
|
||||
b.Go(p.Name(), func() (bool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), hc.timeout)
|
||||
ctx, cancel := context.WithTimeout(hc.ctx, hc.timeout)
|
||||
defer cancel()
|
||||
log.Debugln("Health Checking, proxy: %s, url: %s, id: {%s}", p.Name(), url, uid)
|
||||
_, _ = p.URLTest(ctx, url, expectedStatus)
|
||||
@@ -206,7 +207,7 @@ func (hc *HealthCheck) execute(b *batch.Batch[bool], url, uid string, option *ex
|
||||
}
|
||||
|
||||
func (hc *HealthCheck) close() {
|
||||
hc.done <- struct{}{}
|
||||
hc.ctxCancel()
|
||||
}
|
||||
|
||||
func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint, lazy bool, expectedStatus utils.IntRanges[uint16]) *HealthCheck {
|
||||
@@ -217,8 +218,11 @@ func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint,
|
||||
if timeout == 0 {
|
||||
timeout = 5000
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &HealthCheck{
|
||||
ctx: ctx,
|
||||
ctxCancel: cancel,
|
||||
proxies: proxies,
|
||||
url: url,
|
||||
timeout: time.Duration(timeout) * time.Millisecond,
|
||||
@@ -226,7 +230,6 @@ func NewHealthCheck(proxies []C.Proxy, url string, timeout uint, interval uint,
|
||||
interval: time.Duration(interval) * time.Second,
|
||||
lazy: lazy,
|
||||
expectedStatus: expectedStatus,
|
||||
done: make(chan struct{}, 1),
|
||||
singleDo: singledo.NewSingle[struct{}](time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -9,8 +10,9 @@ import (
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -27,8 +29,20 @@ type healthCheckSchema struct {
|
||||
ExpectedStatus string `provider:"expected-status,omitempty"`
|
||||
}
|
||||
|
||||
type OverrideProxyNameSchema struct {
|
||||
// matching expression for regex replacement
|
||||
Pattern *regexp2.Regexp `provider:"pattern"`
|
||||
// the new content after regex matching
|
||||
Target string `provider:"target"`
|
||||
}
|
||||
|
||||
var _ encoding.TextUnmarshaler = (*regexp2.Regexp)(nil) // ensure *regexp2.Regexp can decode direct by structure package
|
||||
|
||||
type OverrideSchema struct {
|
||||
TFO *bool `provider:"tfo,omitempty"`
|
||||
MPTcp *bool `provider:"mptcp,omitempty"`
|
||||
UDP *bool `provider:"udp,omitempty"`
|
||||
UDPOverTCP *bool `provider:"udp-over-tcp,omitempty"`
|
||||
Up *string `provider:"up,omitempty"`
|
||||
Down *string `provider:"down,omitempty"`
|
||||
DialerProxy *string `provider:"dialer-proxy,omitempty"`
|
||||
@@ -38,20 +52,26 @@ type OverrideSchema struct {
|
||||
IPVersion *string `provider:"ip-version,omitempty"`
|
||||
AdditionalPrefix *string `provider:"additional-prefix,omitempty"`
|
||||
AdditionalSuffix *string `provider:"additional-suffix,omitempty"`
|
||||
|
||||
ProxyName []OverrideProxyNameSchema `provider:"proxy-name,omitempty"`
|
||||
}
|
||||
|
||||
type proxyProviderSchema struct {
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
Type string `provider:"type"`
|
||||
Path string `provider:"path,omitempty"`
|
||||
URL string `provider:"url,omitempty"`
|
||||
Proxy string `provider:"proxy,omitempty"`
|
||||
Interval int `provider:"interval,omitempty"`
|
||||
Filter string `provider:"filter,omitempty"`
|
||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||
DialerProxy string `provider:"dialer-proxy,omitempty"`
|
||||
SizeLimit int64 `provider:"size-limit,omitempty"`
|
||||
Payload []map[string]any `provider:"payload,omitempty"`
|
||||
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override OverrideSchema `provider:"override,omitempty"`
|
||||
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||
Override OverrideSchema `provider:"override,omitempty"`
|
||||
Header map[string][]string `provider:"header,omitempty"`
|
||||
}
|
||||
|
||||
func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvider, error) {
|
||||
@@ -80,32 +100,32 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
}
|
||||
hc := NewHealthCheck([]C.Proxy{}, schema.HealthCheck.URL, uint(schema.HealthCheck.TestTimeout), hcInterval, schema.HealthCheck.Lazy, expectedStatus)
|
||||
|
||||
parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vehicle types.Vehicle
|
||||
switch schema.Type {
|
||||
case "file":
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
vehicle = resource.NewFileVehicle(path)
|
||||
case "http":
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
if schema.Path != "" {
|
||||
path := C.Path.Resolve(schema.Path)
|
||||
if !features.CMFA && !C.Path.IsSafePath(path) {
|
||||
path = C.Path.Resolve(schema.Path)
|
||||
if !C.Path.IsSafePath(path) {
|
||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
} else {
|
||||
path := C.Path.GetPathByHash("proxies", schema.URL)
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path)
|
||||
}
|
||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||
case "inline":
|
||||
return NewInlineProvider(name, schema.Payload, parser, hc)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||
}
|
||||
|
||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
||||
filter := schema.Filter
|
||||
excludeFilter := schema.ExcludeFilter
|
||||
excludeType := schema.ExcludeType
|
||||
dialerProxy := schema.DialerProxy
|
||||
override := schema.Override
|
||||
|
||||
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, override, vehicle, hc)
|
||||
return NewProxySetProvider(name, interval, parser, vehicle, hc)
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package provider
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
suspended bool
|
||||
)
|
||||
|
||||
type UpdatableProvider interface {
|
||||
UpdatedAt() time.Time
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) UpdatedAt() time.Time {
|
||||
return pp.Fetcher.UpdatedAt
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Close() error {
|
||||
pp.healthCheck.close()
|
||||
pp.Fetcher.Destroy()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Close() error {
|
||||
cp.healthCheck.close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Suspend(s bool) {
|
||||
suspended = s
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,11 +13,10 @@ import (
|
||||
"github.com/metacubex/mihomo/adapter"
|
||||
"github.com/metacubex/mihomo/common/convert"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
"github.com/metacubex/mihomo/component/resource"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
types "github.com/metacubex/mihomo/constant/provider"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/tunnel/statistic"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
@@ -32,125 +31,118 @@ type ProxySchema struct {
|
||||
Proxies []map[string]any `yaml:"proxies"`
|
||||
}
|
||||
|
||||
type providerForApi struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
VehicleType string `json:"vehicleType"`
|
||||
Proxies []C.Proxy `json:"proxies"`
|
||||
TestUrl string `json:"testUrl"`
|
||||
ExpectedStatus string `json:"expectedStatus"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
|
||||
}
|
||||
|
||||
type baseProvider struct {
|
||||
name string
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Name() string {
|
||||
return bp.name
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Version() uint32 {
|
||||
return bp.version
|
||||
}
|
||||
|
||||
func (bp *baseProvider) HealthCheck() {
|
||||
bp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Proxies() []C.Proxy {
|
||||
return bp.proxies
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Count() int {
|
||||
return len(bp.proxies)
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Touch() {
|
||||
bp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (bp *baseProvider) HealthCheckURL() string {
|
||||
return bp.healthCheck.url
|
||||
}
|
||||
|
||||
func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
bp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
|
||||
bp.proxies = proxies
|
||||
bp.healthCheck.setProxy(proxies)
|
||||
if bp.healthCheck.auto() {
|
||||
go bp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *baseProvider) Close() error {
|
||||
bp.healthCheck.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxySetProvider for auto gc
|
||||
type ProxySetProvider struct {
|
||||
*proxySetProvider
|
||||
}
|
||||
|
||||
type proxySetProvider struct {
|
||||
baseProvider
|
||||
*resource.Fetcher[[]C.Proxy]
|
||||
proxies []C.Proxy
|
||||
healthCheck *HealthCheck
|
||||
version uint32
|
||||
subscriptionInfo *SubscriptionInfo
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": pp.Name(),
|
||||
"type": pp.Type().String(),
|
||||
"vehicleType": pp.VehicleType().String(),
|
||||
"proxies": pp.Proxies(),
|
||||
"testUrl": pp.healthCheck.url,
|
||||
"expectedStatus": pp.healthCheck.expectedStatus.String(),
|
||||
"updatedAt": pp.UpdatedAt,
|
||||
"subscriptionInfo": pp.subscriptionInfo,
|
||||
return json.Marshal(providerForApi{
|
||||
Name: pp.Name(),
|
||||
Type: pp.Type().String(),
|
||||
VehicleType: pp.VehicleType().String(),
|
||||
Proxies: pp.Proxies(),
|
||||
TestUrl: pp.healthCheck.url,
|
||||
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
|
||||
UpdatedAt: pp.UpdatedAt(),
|
||||
SubscriptionInfo: pp.subscriptionInfo,
|
||||
})
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Version() uint32 {
|
||||
return pp.version
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Name() string {
|
||||
return pp.Fetcher.Name()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) HealthCheck() {
|
||||
pp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Update() error {
|
||||
elm, same, err := pp.Fetcher.Update()
|
||||
if err == nil && !same {
|
||||
pp.OnUpdate(elm)
|
||||
}
|
||||
_, _, err := pp.Fetcher.Update()
|
||||
return err
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Initial() error {
|
||||
elm, err := pp.Fetcher.Initial()
|
||||
_, err := pp.Fetcher.Initial()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pp.OnUpdate(elm)
|
||||
pp.getSubscriptionInfo()
|
||||
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
|
||||
pp.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
|
||||
}
|
||||
pp.closeAllConnections()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
||||
return pp.proxies
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) Touch() {
|
||||
pp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) HealthCheckURL() string {
|
||||
return pp.healthCheck.url
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
pp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) setProxies(proxies []C.Proxy) {
|
||||
pp.proxies = proxies
|
||||
pp.healthCheck.setProxy(proxies)
|
||||
if pp.healthCheck.auto() {
|
||||
go pp.healthCheck.check()
|
||||
}
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) getSubscriptionInfo() {
|
||||
if pp.VehicleType() != types.HTTP {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*90)
|
||||
defer cancel()
|
||||
resp, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {C.UA}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
||||
if userInfoStr == "" {
|
||||
resp2, err := mihomoHttp.HttpRequest(ctx, pp.Vehicle().(*resource.HTTPVehicle).Url(),
|
||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
|
||||
if userInfoStr == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
pp.subscriptionInfo, err = NewSubscriptionInfo(userInfoStr)
|
||||
if err != nil {
|
||||
log.Warnln("[Provider] get subscription-userinfo: %e", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (pp *proxySetProvider) closeAllConnections() {
|
||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||
for _, chain := range c.Chains() {
|
||||
@@ -163,81 +155,134 @@ func (pp *proxySetProvider) closeAllConnections() {
|
||||
})
|
||||
}
|
||||
|
||||
func stopProxyProvider(pd *ProxySetProvider) {
|
||||
pd.healthCheck.close()
|
||||
_ = pd.Fetcher.Destroy()
|
||||
func (pp *proxySetProvider) Close() error {
|
||||
_ = pp.baseProvider.Close()
|
||||
return pp.Fetcher.Close()
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &proxySetProvider{
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
},
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, proxiesOnUpdate(pd))
|
||||
pd.Fetcher = fetcher
|
||||
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
|
||||
httpVehicle.SetInRead(func(resp *http.Response) {
|
||||
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); subscriptionInfo != "" {
|
||||
cachefile.Cache().SetSubscriptionInfo(name, subscriptionInfo)
|
||||
pd.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, stopProxyProvider)
|
||||
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (pp *ProxySetProvider) Close() error {
|
||||
runtime.SetFinalizer(pp, nil)
|
||||
return pp.proxySetProvider.Close()
|
||||
}
|
||||
|
||||
// InlineProvider for auto gc
|
||||
type InlineProvider struct {
|
||||
*inlineProvider
|
||||
}
|
||||
|
||||
type inlineProvider struct {
|
||||
baseProvider
|
||||
updateAt time.Time
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(providerForApi{
|
||||
Name: ip.Name(),
|
||||
Type: ip.Type().String(),
|
||||
VehicleType: ip.VehicleType().String(),
|
||||
Proxies: ip.Proxies(),
|
||||
TestUrl: ip.healthCheck.url,
|
||||
ExpectedStatus: ip.healthCheck.expectedStatus.String(),
|
||||
UpdatedAt: ip.updateAt,
|
||||
})
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) VehicleType() types.VehicleType {
|
||||
return types.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Update() error {
|
||||
// make api update happy
|
||||
ip.updateAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
ps := ProxySchema{Proxies: payload}
|
||||
buf, err := yaml.Marshal(ps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies, err := parser(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip := &inlineProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
updateAt: time.Now(),
|
||||
}
|
||||
wrapper := &InlineProvider{ip}
|
||||
runtime.SetFinalizer(wrapper, (*InlineProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (ip *InlineProvider) Close() error {
|
||||
runtime.SetFinalizer(ip, nil)
|
||||
return ip.baseProvider.Close()
|
||||
}
|
||||
|
||||
// CompatibleProvider for auto gc
|
||||
type CompatibleProvider struct {
|
||||
*compatibleProvider
|
||||
}
|
||||
|
||||
type compatibleProvider struct {
|
||||
name string
|
||||
healthCheck *HealthCheck
|
||||
proxies []C.Proxy
|
||||
version uint32
|
||||
baseProvider
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": cp.Name(),
|
||||
"type": cp.Type().String(),
|
||||
"vehicleType": cp.VehicleType().String(),
|
||||
"proxies": cp.Proxies(),
|
||||
"testUrl": cp.healthCheck.url,
|
||||
"expectedStatus": cp.healthCheck.expectedStatus.String(),
|
||||
return json.Marshal(providerForApi{
|
||||
Name: cp.Name(),
|
||||
Type: cp.Type().String(),
|
||||
VehicleType: cp.VehicleType().String(),
|
||||
Proxies: cp.Proxies(),
|
||||
TestUrl: cp.healthCheck.url,
|
||||
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Version() uint32 {
|
||||
return cp.version
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Name() string {
|
||||
return cp.name
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheck() {
|
||||
cp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
@@ -253,30 +298,6 @@ func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheckURL() string {
|
||||
return cp.healthCheck.url
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func stopCompatibleProvider(pd *CompatibleProvider) {
|
||||
pd.healthCheck.close()
|
||||
}
|
||||
|
||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("provider need one proxy at least")
|
||||
@@ -287,25 +308,49 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
}
|
||||
|
||||
wrapper := &CompatibleProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, stopCompatibleProvider)
|
||||
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvider) Close() error {
|
||||
runtime.SetFinalizer(cp, nil)
|
||||
return cp.compatibleProvider.Close()
|
||||
}
|
||||
|
||||
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||
return func(elm []C.Proxy) {
|
||||
pd.setProxies(elm)
|
||||
pd.version += 1
|
||||
pd.getSubscriptionInfo()
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
|
||||
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||
}
|
||||
var excludeTypeArray []string
|
||||
if excludeType != "" {
|
||||
excludeTypeArray = strings.Split(excludeType, "|")
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
for _, filter := range strings.Split(filter, "`") {
|
||||
filterReg, err := regexp2.Compile(filter, regexp2.None)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid filter regex: %w", err)
|
||||
}
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
@@ -356,12 +401,12 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
continue
|
||||
}
|
||||
if len(excludeFilter) > 0 {
|
||||
if mat, _ := excludeFilterReg.FindStringMatch(name); mat != nil {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(filter) > 0 {
|
||||
if mat, _ := filterReg.FindStringMatch(name); mat == nil {
|
||||
if mat, _ := filterReg.MatchString(name); !mat {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -373,37 +418,33 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
mapping["dialer-proxy"] = dialerProxy
|
||||
}
|
||||
|
||||
if override.UDP != nil {
|
||||
mapping["udp"] = *override.UDP
|
||||
}
|
||||
if override.Up != nil {
|
||||
mapping["up"] = *override.Up
|
||||
}
|
||||
if override.Down != nil {
|
||||
mapping["down"] = *override.Down
|
||||
}
|
||||
if override.DialerProxy != nil {
|
||||
mapping["dialer-proxy"] = *override.DialerProxy
|
||||
}
|
||||
if override.SkipCertVerify != nil {
|
||||
mapping["skip-cert-verify"] = *override.SkipCertVerify
|
||||
}
|
||||
if override.Interface != nil {
|
||||
mapping["interface-name"] = *override.Interface
|
||||
}
|
||||
if override.RoutingMark != nil {
|
||||
mapping["routing-mark"] = *override.RoutingMark
|
||||
}
|
||||
if override.IPVersion != nil {
|
||||
mapping["ip-version"] = *override.IPVersion
|
||||
}
|
||||
if override.AdditionalPrefix != nil {
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = *override.AdditionalPrefix + name
|
||||
}
|
||||
if override.AdditionalSuffix != nil {
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = name + *override.AdditionalSuffix
|
||||
val := reflect.ValueOf(override)
|
||||
for i := 0; i < val.NumField(); i++ {
|
||||
field := val.Field(i)
|
||||
if field.IsNil() {
|
||||
continue
|
||||
}
|
||||
fieldName := strings.Split(val.Type().Field(i).Tag.Get("provider"), ",")[0]
|
||||
switch fieldName {
|
||||
case "additional-prefix":
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = *field.Interface().(*string) + name
|
||||
case "additional-suffix":
|
||||
name := mapping["name"].(string)
|
||||
mapping["name"] = name + *field.Interface().(*string)
|
||||
case "proxy-name":
|
||||
// Iterate through all naming replacement rules and perform the replacements.
|
||||
for _, expr := range override.ProxyName {
|
||||
name := mapping["name"].(string)
|
||||
newName, err := expr.Pattern.Replace(name, expr.Target, 0, -1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("proxy name replace error: %w", err)
|
||||
}
|
||||
mapping["name"] = newName
|
||||
}
|
||||
default:
|
||||
mapping[fieldName] = field.Elem().Interface()
|
||||
}
|
||||
}
|
||||
|
||||
proxy, err := adapter.ParseProxy(mapping)
|
||||
@@ -424,5 +465,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type SubscriptionInfo struct {
|
||||
@@ -12,28 +15,44 @@ type SubscriptionInfo struct {
|
||||
Expire int64
|
||||
}
|
||||
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo, err error) {
|
||||
userinfo = strings.ToLower(userinfo)
|
||||
userinfo = strings.ReplaceAll(userinfo, " ", "")
|
||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "")
|
||||
si = new(SubscriptionInfo)
|
||||
|
||||
for _, field := range strings.Split(userinfo, ";") {
|
||||
switch name, value, _ := strings.Cut(field, "="); name {
|
||||
case "upload":
|
||||
si.Upload, err = strconv.ParseInt(value, 10, 64)
|
||||
case "download":
|
||||
si.Download, err = strconv.ParseInt(value, 10, 64)
|
||||
case "total":
|
||||
si.Total, err = strconv.ParseInt(value, 10, 64)
|
||||
case "expire":
|
||||
if value == "" {
|
||||
si.Expire = 0
|
||||
} else {
|
||||
si.Expire, err = strconv.ParseInt(value, 10, 64)
|
||||
}
|
||||
name, value, ok := strings.Cut(field, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
intValue, err := parseValue(value)
|
||||
if err != nil {
|
||||
return
|
||||
log.Warnln("[Provider] get subscription-userinfo: %e", err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch name {
|
||||
case "upload":
|
||||
si.Upload = intValue
|
||||
case "download":
|
||||
si.Download = intValue
|
||||
case "total":
|
||||
si.Total = intValue
|
||||
case "expire":
|
||||
si.Expire = intValue
|
||||
}
|
||||
}
|
||||
return
|
||||
return si
|
||||
}
|
||||
|
||||
func parseValue(value string) (int64, error) {
|
||||
if intValue, err := strconv.ParseInt(value, 10, 64); err == nil {
|
||||
return intValue, nil
|
||||
}
|
||||
|
||||
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return int64(floatValue), nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("failed to parse value '%s'", value)
|
||||
}
|
||||
|
||||
@@ -33,15 +33,8 @@ type ARC[K comparable, V any] struct {
|
||||
|
||||
// New returns a new Adaptive Replacement Cache (ARC).
|
||||
func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
|
||||
arc := &ARC[K, V]{
|
||||
p: 0,
|
||||
t1: list.New[*entry[K, V]](),
|
||||
b1: list.New[*entry[K, V]](),
|
||||
t2: list.New[*entry[K, V]](),
|
||||
b2: list.New[*entry[K, V]](),
|
||||
len: 0,
|
||||
cache: make(map[K]*entry[K, V]),
|
||||
}
|
||||
arc := &ARC[K, V]{}
|
||||
arc.Clear()
|
||||
|
||||
for _, option := range options {
|
||||
option(arc)
|
||||
@@ -49,6 +42,19 @@ func New[K comparable, V any](options ...Option[K, V]) *ARC[K, V] {
|
||||
return arc
|
||||
}
|
||||
|
||||
func (a *ARC[K, V]) Clear() {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
a.p = 0
|
||||
a.t1 = list.New[*entry[K, V]]()
|
||||
a.b1 = list.New[*entry[K, V]]()
|
||||
a.t2 = list.New[*entry[K, V]]()
|
||||
a.b2 = list.New[*entry[K, V]]()
|
||||
a.len = 0
|
||||
a.cache = make(map[K]*entry[K, V])
|
||||
}
|
||||
|
||||
// Set inserts a new key-value pair into the cache.
|
||||
// This optimizes future access to this entry (side effect).
|
||||
func (a *ARC[K, V]) Set(key K, value V) {
|
||||
|
||||
@@ -330,15 +330,38 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||
|
||||
vmess["h2-opts"] = h2Opts
|
||||
|
||||
case "ws":
|
||||
case "ws", "httpupgrade":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
wsOpts["path"] = []string{"/"}
|
||||
wsOpts["path"] = "/"
|
||||
if host, ok := values["host"]; ok && host != "" {
|
||||
headers["Host"] = host.(string)
|
||||
}
|
||||
if path, ok := values["path"]; ok && path != "" {
|
||||
wsOpts["path"] = path.(string)
|
||||
path := path.(string)
|
||||
pathURL, err := url.Parse(path)
|
||||
if err == nil {
|
||||
query := pathURL.Query()
|
||||
if earlyData := query.Get("ed"); earlyData != "" {
|
||||
med, err := strconv.Atoi(earlyData)
|
||||
if err == nil {
|
||||
switch network {
|
||||
case "ws":
|
||||
wsOpts["max-early-data"] = med
|
||||
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
|
||||
case "httpupgrade":
|
||||
wsOpts["v2ray-http-upgrade-fast-open"] = true
|
||||
}
|
||||
query.Del("ed")
|
||||
pathURL.RawQuery = query.Encode()
|
||||
path = pathURL.String()
|
||||
}
|
||||
}
|
||||
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
|
||||
wsOpts["early-data-header-name"] = earlyDataHeader
|
||||
}
|
||||
}
|
||||
wsOpts["path"] = path
|
||||
}
|
||||
wsOpts["headers"] = headers
|
||||
vmess["ws-opts"] = wsOpts
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/metacubex/sing-shadowsocks/shadowimpl"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
var hostsSuffix = []string{
|
||||
@@ -302,11 +302,11 @@ func RandHost() string {
|
||||
prefix += string(buf[6:8]) + "-"
|
||||
prefix += string(buf[len(buf)-8:])
|
||||
|
||||
return prefix + hostsSuffix[fastrand.Intn(hostsLen)]
|
||||
return prefix + hostsSuffix[randv2.IntN(hostsLen)]
|
||||
}
|
||||
|
||||
func RandUserAgent() string {
|
||||
return userAgents[fastrand.Intn(uaLen)]
|
||||
return userAgents[randv2.IntN(uaLen)]
|
||||
}
|
||||
|
||||
func SetUserAgent(header http.Header) {
|
||||
|
||||
@@ -100,7 +100,7 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
h2Opts["headers"] = headers
|
||||
proxy["h2-opts"] = h2Opts
|
||||
|
||||
case "ws":
|
||||
case "ws", "httpupgrade":
|
||||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
headers["User-Agent"] = RandUserAgent()
|
||||
@@ -113,7 +113,13 @@ func handleVShareLink(names map[string]int, url *url.URL, scheme string, proxy m
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad WebSocket max early data size: %v", err)
|
||||
}
|
||||
wsOpts["max-early-data"] = med
|
||||
switch network {
|
||||
case "ws":
|
||||
wsOpts["max-early-data"] = med
|
||||
wsOpts["early-data-header-name"] = "Sec-WebSocket-Protocol"
|
||||
case "httpupgrade":
|
||||
wsOpts["v2ray-http-upgrade-fast-open"] = true
|
||||
}
|
||||
}
|
||||
if earlyDataHeader := query.Get("eh"); earlyDataHeader != "" {
|
||||
wsOpts["early-data-header-name"] = earlyDataHeader
|
||||
|
||||
@@ -68,10 +68,8 @@ type LruCache[K comparable, V any] struct {
|
||||
|
||||
// New creates an LruCache
|
||||
func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
lc := &LruCache[K, V]{
|
||||
lru: list.New[*entry[K, V]](),
|
||||
cache: make(map[K]*list.Element[*entry[K, V]]),
|
||||
}
|
||||
lc := &LruCache[K, V]{}
|
||||
lc.Clear()
|
||||
|
||||
for _, option := range options {
|
||||
option(lc)
|
||||
@@ -80,6 +78,14 @@ func New[K comparable, V any](options ...Option[K, V]) *LruCache[K, V] {
|
||||
return lc
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) Clear() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.lru = list.New[*entry[K, V]]()
|
||||
c.cache = make(map[K]*list.Element[*entry[K, V]])
|
||||
}
|
||||
|
||||
// Get returns any representation of a cached response and a bool
|
||||
// set to true if the key was found.
|
||||
func (c *LruCache[K, V]) Get(key K) (V, bool) {
|
||||
@@ -223,6 +229,10 @@ func (c *LruCache[K, V]) Delete(key K) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.delete(key)
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) delete(key K) {
|
||||
if le, ok := c.cache[key]; ok {
|
||||
c.deleteElement(le)
|
||||
}
|
||||
@@ -246,13 +256,32 @@ func (c *LruCache[K, V]) deleteElement(le *list.Element[*entry[K, V]]) {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *LruCache[K, V]) Clear() error {
|
||||
// Compute either sets the computed new value for the key or deletes
|
||||
// the value for the key. When the delete result of the valueFn function
|
||||
// is set to true, the value will be deleted, if it exists. When delete
|
||||
// is set to false, the value is updated to the newValue.
|
||||
// The ok result indicates whether value was computed and stored, thus, is
|
||||
// present in the map. The actual result contains the new value in cases where
|
||||
// the value was computed and stored.
|
||||
func (c *LruCache[K, V]) Compute(
|
||||
key K,
|
||||
valueFn func(oldValue V, loaded bool) (newValue V, delete bool),
|
||||
) (actual V, ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.cache = make(map[K]*list.Element[*entry[K, V]])
|
||||
|
||||
return nil
|
||||
if el := c.get(key); el != nil {
|
||||
actual, ok = el.value, true
|
||||
}
|
||||
if newValue, del := valueFn(actual, ok); del {
|
||||
if ok { // data not in cache, so needn't delete
|
||||
c.delete(key)
|
||||
}
|
||||
return lo.Empty[V](), false
|
||||
} else {
|
||||
c.set(key, newValue)
|
||||
return newValue, true
|
||||
}
|
||||
}
|
||||
|
||||
type entry[K comparable, V any] struct {
|
||||
|
||||
@@ -4,11 +4,8 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var KeepAliveInterval = 15 * time.Second
|
||||
|
||||
func SplitNetworkType(s string) (string, string, error) {
|
||||
var (
|
||||
shecme string
|
||||
@@ -47,10 +44,3 @@ func SplitHostPort(s string) (host, port string, hasPort bool, err error) {
|
||||
host, port, err = net.SplitHostPort(temp)
|
||||
return
|
||||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok {
|
||||
_ = tcp.SetKeepAlive(true)
|
||||
_ = tcp.SetKeepAlivePeriod(KeepAliveInterval)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,3 +51,23 @@ func UnMasked(p netip.Prefix) netip.Addr {
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
// PrefixCompare returns an integer comparing two prefixes.
|
||||
// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2.
|
||||
// modify from https://github.com/golang/go/issues/61642#issuecomment-1848587909
|
||||
func PrefixCompare(p, p2 netip.Prefix) int {
|
||||
// compare by validity, address family and prefix base address
|
||||
if c := p.Masked().Addr().Compare(p2.Masked().Addr()); c != 0 {
|
||||
return c
|
||||
}
|
||||
// compare by prefix length
|
||||
f1, f2 := p.Bits(), p2.Bits()
|
||||
if f1 < f2 {
|
||||
return -1
|
||||
}
|
||||
if f1 > f2 {
|
||||
return 1
|
||||
}
|
||||
// compare by prefix address
|
||||
return p.Addr().Compare(p2.Addr())
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package pool
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/randv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
)
|
||||
|
||||
func TestAllocGet(t *testing.T) {
|
||||
@@ -43,6 +43,6 @@ func TestAllocPutThenGet(t *testing.T) {
|
||||
|
||||
func BenchmarkMSB(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
msb(fastrand.Int())
|
||||
msb(randv2.Int())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ func (q *Queue[T]) Copy() []T {
|
||||
|
||||
// Len returns the number of items in this queue.
|
||||
func (q *Queue[T]) Len() int64 {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
return int64(len(q.items))
|
||||
}
|
||||
|
||||
224
common/singleflight/singleflight.go
Normal file
224
common/singleflight/singleflight.go
Normal file
@@ -0,0 +1,224 @@
|
||||
// copy and modify from "golang.org/x/sync/singleflight"
|
||||
|
||||
// Copyright 2013 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 singleflight provides a duplicate function call suppression
|
||||
// mechanism.
|
||||
package singleflight
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// errGoexit indicates the runtime.Goexit was called in
|
||||
// the user given function.
|
||||
var errGoexit = errors.New("runtime.Goexit was called")
|
||||
|
||||
// A panicError is an arbitrary value recovered from a panic
|
||||
// with the stack trace during the execution of given function.
|
||||
type panicError struct {
|
||||
value interface{}
|
||||
stack []byte
|
||||
}
|
||||
|
||||
// Error implements error interface.
|
||||
func (p *panicError) Error() string {
|
||||
return fmt.Sprintf("%v\n\n%s", p.value, p.stack)
|
||||
}
|
||||
|
||||
func (p *panicError) Unwrap() error {
|
||||
err, ok := p.value.(error)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func newPanicError(v interface{}) error {
|
||||
stack := debug.Stack()
|
||||
|
||||
// The first line of the stack trace is of the form "goroutine N [status]:"
|
||||
// but by the time the panic reaches Do the goroutine may no longer exist
|
||||
// and its status will have changed. Trim out the misleading line.
|
||||
if line := bytes.IndexByte(stack[:], '\n'); line >= 0 {
|
||||
stack = stack[line+1:]
|
||||
}
|
||||
return &panicError{value: v, stack: stack}
|
||||
}
|
||||
|
||||
// call is an in-flight or completed singleflight.Do call
|
||||
type call[T any] struct {
|
||||
wg sync.WaitGroup
|
||||
|
||||
// These fields are written once before the WaitGroup is done
|
||||
// and are only read after the WaitGroup is done.
|
||||
val T
|
||||
err error
|
||||
|
||||
// These fields are read and written with the singleflight
|
||||
// mutex held before the WaitGroup is done, and are read but
|
||||
// not written after the WaitGroup is done.
|
||||
dups int
|
||||
chans []chan<- Result[T]
|
||||
}
|
||||
|
||||
// Group represents a class of work and forms a namespace in
|
||||
// which units of work can be executed with duplicate suppression.
|
||||
type Group[T any] struct {
|
||||
mu sync.Mutex // protects m
|
||||
m map[string]*call[T] // lazily initialized
|
||||
|
||||
StoreResult bool
|
||||
}
|
||||
|
||||
// Result holds the results of Do, so they can be passed
|
||||
// on a channel.
|
||||
type Result[T any] struct {
|
||||
Val T
|
||||
Err error
|
||||
Shared bool
|
||||
}
|
||||
|
||||
// Do executes and returns the results of the given function, making
|
||||
// sure that only one execution is in-flight for a given key at a
|
||||
// time. If a duplicate comes in, the duplicate caller waits for the
|
||||
// original to complete and receives the same results.
|
||||
// The return value shared indicates whether v was given to multiple callers.
|
||||
func (g *Group[T]) Do(key string, fn func() (T, error)) (v T, err error, shared bool) {
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call[T])
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.dups++
|
||||
g.mu.Unlock()
|
||||
c.wg.Wait()
|
||||
|
||||
if e, ok := c.err.(*panicError); ok {
|
||||
panic(e)
|
||||
} else if c.err == errGoexit {
|
||||
runtime.Goexit()
|
||||
}
|
||||
return c.val, c.err, true
|
||||
}
|
||||
c := new(call[T])
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.mu.Unlock()
|
||||
|
||||
g.doCall(c, key, fn)
|
||||
return c.val, c.err, c.dups > 0
|
||||
}
|
||||
|
||||
// DoChan is like Do but returns a channel that will receive the
|
||||
// results when they are ready.
|
||||
//
|
||||
// The returned channel will not be closed.
|
||||
func (g *Group[T]) DoChan(key string, fn func() (T, error)) <-chan Result[T] {
|
||||
ch := make(chan Result[T], 1)
|
||||
g.mu.Lock()
|
||||
if g.m == nil {
|
||||
g.m = make(map[string]*call[T])
|
||||
}
|
||||
if c, ok := g.m[key]; ok {
|
||||
c.dups++
|
||||
c.chans = append(c.chans, ch)
|
||||
g.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
c := &call[T]{chans: []chan<- Result[T]{ch}}
|
||||
c.wg.Add(1)
|
||||
g.m[key] = c
|
||||
g.mu.Unlock()
|
||||
|
||||
go g.doCall(c, key, fn)
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// doCall handles the single call for a key.
|
||||
func (g *Group[T]) doCall(c *call[T], key string, fn func() (T, error)) {
|
||||
normalReturn := false
|
||||
recovered := false
|
||||
|
||||
// use double-defer to distinguish panic from runtime.Goexit,
|
||||
// more details see https://golang.org/cl/134395
|
||||
defer func() {
|
||||
// the given function invoked runtime.Goexit
|
||||
if !normalReturn && !recovered {
|
||||
c.err = errGoexit
|
||||
}
|
||||
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
c.wg.Done()
|
||||
if g.m[key] == c && !g.StoreResult {
|
||||
delete(g.m, key)
|
||||
}
|
||||
|
||||
if e, ok := c.err.(*panicError); ok {
|
||||
// In order to prevent the waiting channels from being blocked forever,
|
||||
// needs to ensure that this panic cannot be recovered.
|
||||
if len(c.chans) > 0 {
|
||||
go panic(e)
|
||||
select {} // Keep this goroutine around so that it will appear in the crash dump.
|
||||
} else {
|
||||
panic(e)
|
||||
}
|
||||
} else if c.err == errGoexit {
|
||||
// Already in the process of goexit, no need to call again
|
||||
} else {
|
||||
// Normal return
|
||||
for _, ch := range c.chans {
|
||||
ch <- Result[T]{c.val, c.err, c.dups > 0}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
func() {
|
||||
defer func() {
|
||||
if !normalReturn {
|
||||
// Ideally, we would wait to take a stack trace until we've determined
|
||||
// whether this is a panic or a runtime.Goexit.
|
||||
//
|
||||
// Unfortunately, the only way we can distinguish the two is to see
|
||||
// whether the recover stopped the goroutine from terminating, and by
|
||||
// the time we know that, the part of the stack trace relevant to the
|
||||
// panic has been discarded.
|
||||
if r := recover(); r != nil {
|
||||
c.err = newPanicError(r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.val, c.err = fn()
|
||||
normalReturn = true
|
||||
}()
|
||||
|
||||
if !normalReturn {
|
||||
recovered = true
|
||||
}
|
||||
}
|
||||
|
||||
// Forget tells the singleflight to forget about a key. Future calls
|
||||
// to Do for this key will call the function rather than waiting for
|
||||
// an earlier call to complete.
|
||||
func (g *Group[T]) Forget(key string) {
|
||||
g.mu.Lock()
|
||||
delete(g.m, key)
|
||||
g.mu.Unlock()
|
||||
}
|
||||
|
||||
func (g *Group[T]) Reset() {
|
||||
g.mu.Lock()
|
||||
g.m = nil
|
||||
g.mu.Unlock()
|
||||
}
|
||||
30
common/sockopt/reuse_common.go
Normal file
30
common/sockopt/reuse_common.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func RawConnReuseaddr(rc syscall.RawConn) (err error) {
|
||||
var innerErr error
|
||||
err = rc.Control(func(fd uintptr) {
|
||||
innerErr = reuseControl(fd)
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
err = innerErr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UDPReuseaddr(c net.PacketConn) error {
|
||||
if c, ok := c.(syscall.Conn); ok {
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RawConnReuseaddr(rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
|
||||
|
||||
package dialer
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(*net.ListenConfig) {}
|
||||
func reuseControl(fd uintptr) error { return nil }
|
||||
22
common/sockopt/reuse_unix.go
Normal file
22
common/sockopt/reuse_unix.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func reuseControl(fd uintptr) error {
|
||||
e1 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
e2 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
9
common/sockopt/reuse_windows.go
Normal file
9
common/sockopt/reuse_windows.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func reuseControl(fd uintptr) error {
|
||||
return windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||
return
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package structure
|
||||
// references: https://github.com/mitchellh/mapstructure
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -85,36 +86,62 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
||||
kind := val.Kind()
|
||||
switch {
|
||||
case isInt(kind):
|
||||
return d.decodeInt(name, data, val)
|
||||
case isUint(kind):
|
||||
return d.decodeUint(name, data, val)
|
||||
case isFloat(kind):
|
||||
return d.decodeFloat(name, data, val)
|
||||
// isNil returns true if the input is nil or a typed nil pointer.
|
||||
func isNil(input any) bool {
|
||||
if input == nil {
|
||||
return true
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Pointer:
|
||||
if val.IsNil() {
|
||||
val := reflect.ValueOf(input)
|
||||
return val.Kind() == reflect.Pointer && val.IsNil()
|
||||
}
|
||||
|
||||
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
||||
if isNil(data) {
|
||||
// If the data is nil, then we don't set anything
|
||||
// Maybe we should set to zero value?
|
||||
return nil
|
||||
}
|
||||
if !reflect.ValueOf(data).IsValid() {
|
||||
// If the input value is invalid, then we just set the value
|
||||
// to be the zero value.
|
||||
val.Set(reflect.Zero(val.Type()))
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
kind := val.Kind()
|
||||
if kind == reflect.Pointer && val.IsNil() {
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
return d.decode(name, data, val.Elem())
|
||||
case reflect.String:
|
||||
return d.decodeString(name, data, val)
|
||||
case reflect.Bool:
|
||||
return d.decodeBool(name, data, val)
|
||||
case reflect.Slice:
|
||||
return d.decodeSlice(name, data, val)
|
||||
case reflect.Map:
|
||||
return d.decodeMap(name, data, val)
|
||||
case reflect.Interface:
|
||||
return d.setInterface(name, data, val)
|
||||
case reflect.Struct:
|
||||
return d.decodeStruct(name, data, val)
|
||||
default:
|
||||
return fmt.Errorf("type %s not support", val.Kind().String())
|
||||
if ok, err := d.decodeTextUnmarshaller(name, data, val); ok {
|
||||
return err
|
||||
}
|
||||
switch {
|
||||
case isInt(kind):
|
||||
return d.decodeInt(name, data, val)
|
||||
case isUint(kind):
|
||||
return d.decodeUint(name, data, val)
|
||||
case isFloat(kind):
|
||||
return d.decodeFloat(name, data, val)
|
||||
}
|
||||
switch kind {
|
||||
case reflect.Pointer:
|
||||
val = val.Elem()
|
||||
continue
|
||||
case reflect.String:
|
||||
return d.decodeString(name, data, val)
|
||||
case reflect.Bool:
|
||||
return d.decodeBool(name, data, val)
|
||||
case reflect.Slice:
|
||||
return d.decodeSlice(name, data, val)
|
||||
case reflect.Map:
|
||||
return d.decodeMap(name, data, val)
|
||||
case reflect.Interface:
|
||||
return d.setInterface(name, data, val)
|
||||
case reflect.Struct:
|
||||
return d.decodeStruct(name, data, val)
|
||||
default:
|
||||
return fmt.Errorf("type %s not support", val.Kind().String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,3 +580,25 @@ func (d *Decoder) setInterface(name string, data any, val reflect.Value) (err er
|
||||
val.Set(dataVal)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeTextUnmarshaller(name string, data any, val reflect.Value) (bool, error) {
|
||||
if !val.CanAddr() {
|
||||
return false, nil
|
||||
}
|
||||
valAddr := val.Addr()
|
||||
if !valAddr.CanInterface() {
|
||||
return false, nil
|
||||
}
|
||||
unmarshaller, ok := valAddr.Interface().(encoding.TextUnmarshaler)
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
var str string
|
||||
if err := d.decodeString(name, data, reflect.Indirect(reflect.ValueOf(&str))); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := unmarshaller.UnmarshalText([]byte(str)); err != nil {
|
||||
return true, fmt.Errorf("cannot parse '%s' as %s: %s", name, val.Type(), err)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package structure
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -179,3 +180,108 @@ func TestStructure_SliceNilValueComplex(t *testing.T) {
|
||||
err = decoder.Decode(rawMap, ss)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestStructure_SliceCap(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"foo": []string{},
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Foo []string `test:"foo,omitempty"`
|
||||
Bar []string `test:"bar,omitempty"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, s.Foo) // structure's Decode will ensure value not nil when input has value even it was set an empty array
|
||||
assert.Nil(t, s.Bar)
|
||||
}
|
||||
|
||||
func TestStructure_Base64(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"foo": "AQID",
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Foo []byte `test:"foo"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []byte{1, 2, 3}, s.Foo)
|
||||
}
|
||||
|
||||
func TestStructure_Pointer(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"foo": "foo",
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Foo *string `test:"foo,omitempty"`
|
||||
Bar *string `test:"bar,omitempty"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, s.Foo)
|
||||
assert.Equal(t, "foo", *s.Foo)
|
||||
assert.Nil(t, s.Bar)
|
||||
}
|
||||
|
||||
type num struct {
|
||||
a int
|
||||
}
|
||||
|
||||
func (n *num) UnmarshalText(text []byte) (err error) {
|
||||
n.a, err = strconv.Atoi(string(text))
|
||||
return
|
||||
}
|
||||
|
||||
func TestStructure_TextUnmarshaller(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"num": "255",
|
||||
"num_p": "127",
|
||||
}
|
||||
|
||||
s := &struct {
|
||||
Num num `test:"num"`
|
||||
NumP *num `test:"num_p"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 255, s.Num.a)
|
||||
assert.NotNil(t, s.NumP)
|
||||
assert.Equal(t, s.NumP.a, 127)
|
||||
|
||||
// test WeaklyTypedInput
|
||||
rawMap["num"] = 256
|
||||
err = decoder.Decode(rawMap, s)
|
||||
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||
err = weakTypeDecoder.Decode(rawMap, s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 256, s.Num.a)
|
||||
|
||||
// test invalid input
|
||||
rawMap["num_p"] = "abc"
|
||||
err = decoder.Decode(rawMap, s)
|
||||
assert.NotNilf(t, err, "should throw error: %#v", s)
|
||||
}
|
||||
|
||||
func TestStructure_Null(t *testing.T) {
|
||||
rawMap := map[string]any{
|
||||
"opt": map[string]any{
|
||||
"bar": nil,
|
||||
},
|
||||
}
|
||||
|
||||
s := struct {
|
||||
Opt struct {
|
||||
Bar string `test:"bar,optional"`
|
||||
} `test:"opt,optional"`
|
||||
}{}
|
||||
|
||||
err := decoder.Decode(rawMap, &s)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, s.Opt.Bar, "")
|
||||
}
|
||||
|
||||
50
common/utils/callback.go
Normal file
50
common/utils/callback.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
list "github.com/bahlo/generic-list-go"
|
||||
)
|
||||
|
||||
type Callback[T any] struct {
|
||||
list list.List[func(T)]
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
func NewCallback[T any]() *Callback[T] {
|
||||
return &Callback[T]{}
|
||||
}
|
||||
|
||||
func (c *Callback[T]) Register(item func(T)) io.Closer {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
element := c.list.PushBack(item)
|
||||
return &callbackCloser[T]{
|
||||
element: element,
|
||||
callback: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Callback[T]) Emit(item T) {
|
||||
c.mutex.RLock()
|
||||
defer c.mutex.RUnlock()
|
||||
for element := c.list.Front(); element != nil; element = element.Next() {
|
||||
go element.Value(item)
|
||||
}
|
||||
}
|
||||
|
||||
type callbackCloser[T any] struct {
|
||||
element *list.Element[func(T)]
|
||||
callback *Callback[T]
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func (c *callbackCloser[T]) Close() error {
|
||||
c.once.Do(func() {
|
||||
c.callback.mutex.Lock()
|
||||
defer c.callback.mutex.Unlock()
|
||||
c.callback.list.Remove(c.element)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
62
common/utils/hash.go
Normal file
62
common/utils/hash.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// HashType warps hash array inside struct
|
||||
// someday can change to other hash algorithm simply
|
||||
type HashType struct {
|
||||
md5 [md5.Size]byte // MD5
|
||||
}
|
||||
|
||||
func MakeHash(data []byte) HashType {
|
||||
return HashType{md5.Sum(data)}
|
||||
}
|
||||
|
||||
func (h HashType) Equal(hash HashType) bool {
|
||||
return h.md5 == hash.md5
|
||||
}
|
||||
|
||||
func (h HashType) Bytes() []byte {
|
||||
return h.md5[:]
|
||||
}
|
||||
|
||||
func (h HashType) String() string {
|
||||
return hex.EncodeToString(h.Bytes())
|
||||
}
|
||||
|
||||
func (h HashType) MarshalText() ([]byte, error) {
|
||||
return []byte(h.String()), nil
|
||||
}
|
||||
|
||||
func (h *HashType) UnmarshalText(data []byte) error {
|
||||
if hex.DecodedLen(len(data)) != md5.Size {
|
||||
return errors.New("invalid hash length")
|
||||
}
|
||||
_, err := hex.Decode(h.md5[:], data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h HashType) MarshalBinary() ([]byte, error) {
|
||||
return h.md5[:], nil
|
||||
}
|
||||
|
||||
func (h *HashType) UnmarshalBinary(data []byte) error {
|
||||
if len(data) != md5.Size {
|
||||
return errors.New("invalid hash length")
|
||||
}
|
||||
copy(h.md5[:], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h HashType) Len() int {
|
||||
return len(h.md5)
|
||||
}
|
||||
|
||||
func (h HashType) IsValid() bool {
|
||||
var zero HashType
|
||||
return h != zero
|
||||
}
|
||||
@@ -139,10 +139,13 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
|
||||
}
|
||||
|
||||
for _, r := range ranges {
|
||||
for i := r.Start(); i <= r.End(); i++ {
|
||||
for i := r.Start(); i <= r.End() && i >= r.Start(); i++ {
|
||||
if !f(i) {
|
||||
return
|
||||
}
|
||||
if i+1 < i { // integer overflow
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,39 @@ package utils
|
||||
|
||||
import (
|
||||
"github.com/gofrs/uuid/v5"
|
||||
"github.com/zhangyunhao116/fastrand"
|
||||
"github.com/metacubex/randv2"
|
||||
)
|
||||
|
||||
type fastRandReader struct{}
|
||||
type unsafeRandReader struct{}
|
||||
|
||||
func (r fastRandReader) Read(p []byte) (int, error) {
|
||||
return fastrand.Read(p)
|
||||
func (r unsafeRandReader) Read(p []byte) (n int, err error) {
|
||||
// modify from https://github.com/golang/go/blob/587c3847da81aa7cfc3b3db2677c8586c94df13a/src/runtime/rand.go#L70-L89
|
||||
// Inspired by wyrand.
|
||||
n = len(p)
|
||||
v := randv2.Uint64()
|
||||
for len(p) > 0 {
|
||||
v ^= 0xa0761d6478bd642f
|
||||
v *= 0xe7037ed1a0b428db
|
||||
size := 8
|
||||
if len(p) < 8 {
|
||||
size = len(p)
|
||||
}
|
||||
for i := 0; i < size; i++ {
|
||||
p[i] ^= byte(v >> (8 * i))
|
||||
}
|
||||
p = p[size:]
|
||||
v = v>>32 | v<<32
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(fastRandReader{}))
|
||||
var UnsafeRandReader = unsafeRandReader{}
|
||||
|
||||
var UnsafeUUIDGenerator = uuid.NewGenWithOptions(uuid.WithRandomReader(UnsafeRandReader))
|
||||
|
||||
func NewUUIDV1() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV1() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV1() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -23,7 +43,7 @@ func NewUUIDV3(ns uuid.UUID, name string) uuid.UUID {
|
||||
}
|
||||
|
||||
func NewUUIDV4() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV4() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV4() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
@@ -32,12 +52,12 @@ func NewUUIDV5(ns uuid.UUID, name string) uuid.UUID {
|
||||
}
|
||||
|
||||
func NewUUIDV6() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV6() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV6() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
func NewUUIDV7() uuid.UUID {
|
||||
u, _ := UnsafeUUIDGenerator.NewV7() // fastrand.Read wouldn't cause error, so ignore err is safe
|
||||
u, _ := UnsafeUUIDGenerator.NewV7() // unsafeRandReader wouldn't cause error, so ignore err is safe
|
||||
return u
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,11 @@ type Authenticator interface {
|
||||
Users() []string
|
||||
}
|
||||
|
||||
type AuthStore interface {
|
||||
Authenticator() Authenticator
|
||||
SetAuthenticator(Authenticator)
|
||||
}
|
||||
|
||||
type AuthUser struct {
|
||||
User string
|
||||
Pass string
|
||||
|
||||
@@ -13,9 +13,10 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
)
|
||||
|
||||
var trustCerts []*x509.Certificate
|
||||
var globalCertPool *x509.CertPool
|
||||
var mutex sync.RWMutex
|
||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||
@@ -28,11 +29,19 @@ var DisableSystemCa, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_CA"))
|
||||
func AddCertificate(certificate string) error {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
if certificate == "" {
|
||||
return fmt.Errorf("certificate is empty")
|
||||
}
|
||||
if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
|
||||
trustCerts = append(trustCerts, cert)
|
||||
|
||||
if globalCertPool == nil {
|
||||
initializeCertPool()
|
||||
}
|
||||
|
||||
if globalCertPool.AppendCertsFromPEM([]byte(certificate)) {
|
||||
return nil
|
||||
} else if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
|
||||
globalCertPool.AddCert(cert)
|
||||
return nil
|
||||
} else {
|
||||
return fmt.Errorf("add certificate failed")
|
||||
@@ -49,9 +58,6 @@ func initializeCertPool() {
|
||||
globalCertPool = x509.NewCertPool()
|
||||
}
|
||||
}
|
||||
for _, cert := range trustCerts {
|
||||
globalCertPool.AddCert(cert)
|
||||
}
|
||||
if !DisableEmbedCa {
|
||||
globalCertPool.AppendCertsFromPEM(_CaCertificates)
|
||||
}
|
||||
@@ -60,14 +66,10 @@ func initializeCertPool() {
|
||||
func ResetCertificate() {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
trustCerts = nil
|
||||
initializeCertPool()
|
||||
}
|
||||
|
||||
func getCertPool() *x509.CertPool {
|
||||
if len(trustCerts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if globalCertPool == nil {
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
@@ -117,7 +119,7 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
|
||||
var certificate []byte
|
||||
var err error
|
||||
if len(customCA) > 0 {
|
||||
certificate, err = os.ReadFile(customCA)
|
||||
certificate, err = os.ReadFile(C.Path.Resolve(customCA))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load ca error: %w", err)
|
||||
}
|
||||
|
||||
14
component/ca/fix_windows.go
Normal file
14
component/ca/fix_windows.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package ca
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// crypto/x509: certificate validation in Windows fails to validate IP in SAN
|
||||
// https://github.com/golang/go/issues/37176
|
||||
// As far as I can tell this is still the case on most older versions of Windows (but seems to be fixed in 10)
|
||||
if features.WindowsMajorVersion < 10 && len(_CaCertificates) > 0 {
|
||||
DisableSystemCa = true
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,20 @@ func (set *IpCidrSet) IsContainForString(ipString string) bool {
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) IsContain(ip netip.Addr) bool {
|
||||
return set.toIPSet().Contains(ip.WithZone(""))
|
||||
return set.ToIPSet().Contains(ip.WithZone(""))
|
||||
}
|
||||
|
||||
// MatchIp implements C.IpMatcher
|
||||
func (set *IpCidrSet) MatchIp(ip netip.Addr) bool {
|
||||
if set.IsEmpty() {
|
||||
return false
|
||||
}
|
||||
return set.IsContain(ip)
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) Merge() error {
|
||||
var b netipx.IPSetBuilder
|
||||
b.AddSet(set.toIPSet())
|
||||
b.AddSet(set.ToIPSet())
|
||||
i, err := b.IPSet()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -57,7 +65,23 @@ func (set *IpCidrSet) Merge() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) toIPSet() *netipx.IPSet {
|
||||
func (set *IpCidrSet) IsEmpty() bool {
|
||||
return set == nil || len(set.rr) == 0
|
||||
}
|
||||
|
||||
func (set *IpCidrSet) Foreach(f func(prefix netip.Prefix) bool) {
|
||||
for _, r := range set.rr {
|
||||
for _, prefix := range r.Prefixes() {
|
||||
if !f(prefix) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ToIPSet not safe convert to *netipx.IPSet
|
||||
// be careful, must be used after Merge
|
||||
func (set *IpCidrSet) ToIPSet() *netipx.IPSet {
|
||||
return (*netipx.IPSet)(unsafe.Pointer(set))
|
||||
}
|
||||
|
||||
|
||||
77
component/cidr/ipcidr_set_bin.go
Normal file
77
component/cidr/ipcidr_set_bin.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package cidr
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net/netip"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
func (ss *IpCidrSet) WriteBin(w io.Writer) (err error) {
|
||||
// version
|
||||
_, err = w.Write([]byte{1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// rr
|
||||
err = binary.Write(w, binary.BigEndian, int64(len(ss.rr)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range ss.rr {
|
||||
err = binary.Write(w, binary.BigEndian, r.From().As16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = binary.Write(w, binary.BigEndian, r.To().As16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadIpCidrSet(r io.Reader) (ss *IpCidrSet, err error) {
|
||||
// version
|
||||
version := make([]byte, 1)
|
||||
_, err = io.ReadFull(r, version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if version[0] != 1 {
|
||||
return nil, errors.New("version is invalid")
|
||||
}
|
||||
|
||||
ss = NewIpCidrSet()
|
||||
var length int64
|
||||
|
||||
// rr
|
||||
err = binary.Read(r, binary.BigEndian, &length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if length < 1 {
|
||||
return nil, errors.New("length is invalid")
|
||||
}
|
||||
ss.rr = make([]netipx.IPRange, length)
|
||||
for i := int64(0); i < length; i++ {
|
||||
var a16 [16]byte
|
||||
err = binary.Read(r, binary.BigEndian, &a16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
from := netip.AddrFrom16(a16).Unmap()
|
||||
err = binary.Read(r, binary.BigEndian, &a16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to := netip.AddrFrom16(a16).Unmap()
|
||||
ss.rr[i] = netipx.IPRangeFrom(from, to)
|
||||
}
|
||||
|
||||
return ss, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package dhcp
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
@@ -24,5 +25,5 @@ func ListenDHCPClient(ctx context.Context, ifaceName string) (net.PacketConn, er
|
||||
options = append(options, dialer.WithFallbackBind(true))
|
||||
}
|
||||
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, options...)
|
||||
return dialer.ListenPacket(ctx, "udp4", listenAddr, netip.AddrPortFrom(netip.AddrFrom4([4]byte{255, 255, 255, 255}), 67), options...)
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func fallbackBindIfaceToDialer(ifaceName string, dialer *net.Dialer, network str
|
||||
return nil
|
||||
}
|
||||
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string) (string, error) {
|
||||
func fallbackBindIfaceToListenConfig(ifaceName string, _ *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
_, port, err := net.SplitHostPort(address)
|
||||
if err != nil {
|
||||
port = "0"
|
||||
|
||||
@@ -46,7 +46,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
||||
@@ -35,7 +35,7 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.A
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
addControlToListenConfig(lc, bindControl(ifaceName))
|
||||
|
||||
return address, nil
|
||||
|
||||
@@ -11,8 +11,8 @@ func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, network string, des
|
||||
return fallbackBindIfaceToDialer(ifaceName, dialer, network, destination)
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address)
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, network, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
return fallbackBindIfaceToListenConfig(ifaceName, lc, network, address, rAddrPort)
|
||||
}
|
||||
|
||||
func ParseNetwork(network string, addr netip.Addr) string {
|
||||
|
||||
@@ -36,7 +36,7 @@ func bind6(handle syscall.Handle, ifaceIdx int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func bindControl(ifaceIdx int) controlFn {
|
||||
func bindControl(ifaceIdx int, rAddrPort netip.AddrPort) controlFn {
|
||||
return func(ctx context.Context, network, address string, c syscall.RawConn) (err error) {
|
||||
addrPort, err := netip.ParseAddrPort(address)
|
||||
if err == nil && !addrPort.Addr().IsGlobalUnicast() {
|
||||
@@ -55,7 +55,7 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
innerErr = bind4err
|
||||
case "udp6":
|
||||
// golang will set network to udp6 when listenUDP on wildcard ip (eg: ":0", "")
|
||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil {
|
||||
if (!addrPort.Addr().IsValid() || addrPort.Addr().IsUnspecified()) && bind6err != nil && rAddrPort.Addr().Unmap().Is4() {
|
||||
// try bind ipv6, if failed, ignore. it's a workaround for windows disable interface ipv6
|
||||
if bind4err != nil {
|
||||
innerErr = fmt.Errorf("%w (%s)", bind6err, bind4err)
|
||||
@@ -76,23 +76,23 @@ func bindControl(ifaceIdx int) controlFn {
|
||||
}
|
||||
}
|
||||
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, _ netip.Addr) error {
|
||||
func bindIfaceToDialer(ifaceName string, dialer *net.Dialer, _ string, destination netip.Addr) error {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addControlToDialer(dialer, bindControl(ifaceObj.Index))
|
||||
addControlToDialer(dialer, bindControl(ifaceObj.Index, netip.AddrPortFrom(destination, 0)))
|
||||
return nil
|
||||
}
|
||||
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string) (string, error) {
|
||||
func bindIfaceToListenConfig(ifaceName string, lc *net.ListenConfig, _, address string, rAddrPort netip.AddrPort) (string, error) {
|
||||
ifaceObj, err := iface.ResolveInterface(ifaceName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index))
|
||||
addControlToListenConfig(lc, bindControl(ifaceObj.Index, rAddrPort))
|
||||
return address, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/constant/features"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
@@ -78,30 +78,30 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||
}
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, options ...Option) (net.PacketConn, error) {
|
||||
if features.CMFA && DefaultSocketHook != nil {
|
||||
return listenPacketHooked(ctx, network, address)
|
||||
}
|
||||
|
||||
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
|
||||
cfg := applyOptions(options...)
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = addr
|
||||
}
|
||||
if cfg.addrReuse {
|
||||
addrReuseToListenConfig(lc)
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
|
||||
socketHookToListenConfig(lc)
|
||||
} else {
|
||||
if cfg.interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = addr
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
@@ -127,17 +127,11 @@ func GetTcpConcurrent() bool {
|
||||
}
|
||||
|
||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
||||
if features.CMFA && DefaultSocketHook != nil {
|
||||
return dialContextHooked(ctx, network, destination, port)
|
||||
}
|
||||
|
||||
var address string
|
||||
if IP4PEnable {
|
||||
NewDestination, NewPort := lookupIP4P(destination.String(), port)
|
||||
address = net.JoinHostPort(NewDestination, NewPort)
|
||||
} else {
|
||||
address = net.JoinHostPort(destination.String(), port)
|
||||
destination, port = lookupIP4P(destination, port)
|
||||
}
|
||||
address = net.JoinHostPort(destination.String(), port)
|
||||
|
||||
netDialer := opt.netDialer
|
||||
switch netDialer.(type) {
|
||||
@@ -151,24 +145,31 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
}
|
||||
|
||||
dialer := netDialer.(*net.Dialer)
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
keepalive.SetNetDialer(dialer)
|
||||
if opt.mpTcp {
|
||||
setMultiPathTCP(dialer)
|
||||
}
|
||||
if opt.tfo {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
|
||||
socketHookToToDialer(dialer)
|
||||
} else {
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||
}
|
||||
if opt.tfo && !DisableTFO {
|
||||
return dialTFO(ctx, *dialer, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
}
|
||||
|
||||
@@ -339,26 +340,18 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
|
||||
return nil, "-1", err
|
||||
}
|
||||
|
||||
if preferResolver == nil {
|
||||
preferResolver = resolver.ProxyServerHostResolver
|
||||
}
|
||||
|
||||
var ips []netip.Addr
|
||||
switch network {
|
||||
case "tcp4", "udp4":
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
||||
case "tcp6", "udp6":
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
||||
default:
|
||||
if preferResolver == nil {
|
||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
||||
} else {
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
||||
@@ -380,12 +373,12 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (net.C
|
||||
}
|
||||
|
||||
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||
opt := WithOption(d.Opt)
|
||||
opt := d.Opt // make a copy
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
opt = WithInterface("")
|
||||
WithInterface("")(&opt)
|
||||
}
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, opt)
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
|
||||
}
|
||||
|
||||
func NewDialer(options ...Option) Dialer {
|
||||
@@ -399,13 +392,13 @@ func GetIP4PEnable(enableIP4PConvert bool) {
|
||||
|
||||
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
|
||||
|
||||
func lookupIP4P(addr string, port string) (string, string) {
|
||||
ip := net.ParseIP(addr)
|
||||
func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
|
||||
ip := addr.AsSlice()
|
||||
if ip[0] == 0x20 && ip[1] == 0x01 &&
|
||||
ip[2] == 0x00 && ip[3] == 0x00 {
|
||||
addr = net.IPv4(ip[12], ip[13], ip[14], ip[15]).String()
|
||||
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
|
||||
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
|
||||
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr, port))
|
||||
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
|
||||
return addr, port
|
||||
}
|
||||
return addr, port
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
//go:build android && cmfa
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
||||
dialer := &net.Dialer{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
conn, err := dialer.DialContext(ctx, network, net.JoinHostPort(destination.String(), port))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t, ok := conn.(*net.TCPConn); ok {
|
||||
t.SetKeepAlive(false)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
lc := &net.ListenConfig{
|
||||
Control: DefaultSocketHook,
|
||||
}
|
||||
|
||||
return lc.ListenPacket(ctx, network, address)
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
//go:build !(android && cmfa)
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func dialContextHooked(ctx context.Context, network string, destination netip.Addr, port string) (net.Conn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func listenPacketHooked(ctx context.Context, network, address string) (net.PacketConn, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We must use this DialContext to query DNS
|
||||
// when using net default resolver.
|
||||
net.DefaultResolver.PreferGo = true
|
||||
net.DefaultResolver.Dial = resolverDialContext
|
||||
}
|
||||
|
||||
func resolverDialContext(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
d := &net.Dialer{}
|
||||
|
||||
interfaceName := DefaultInterface.Load()
|
||||
|
||||
if interfaceName != "" {
|
||||
dstIP, err := netip.ParseAddr(address)
|
||||
if err == nil {
|
||||
_ = bindIfaceToDialer(interfaceName, d, network, dstIP)
|
||||
}
|
||||
}
|
||||
|
||||
return d.DialContext(ctx, network, address)
|
||||
}
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"github.com/metacubex/mihomo/common/sockopt"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||
})
|
||||
return sockopt.RawConnReuseaddr(c)
|
||||
})
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
27
component/dialer/socket_hook.go
Normal file
27
component/dialer/socket_hook.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// SocketControl
|
||||
// never change type traits because it's used in CMFA
|
||||
type SocketControl func(network, address string, conn syscall.RawConn) error
|
||||
|
||||
// DefaultSocketHook
|
||||
// never change type traits because it's used in CMFA
|
||||
var DefaultSocketHook SocketControl
|
||||
|
||||
func socketHookToToDialer(dialer *net.Dialer) {
|
||||
addControlToDialer(dialer, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return DefaultSocketHook(network, address, c)
|
||||
})
|
||||
}
|
||||
|
||||
func socketHookToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return DefaultSocketHook(network, address, c)
|
||||
})
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"github.com/metacubex/tfo-go"
|
||||
)
|
||||
|
||||
var DisableTFO = false
|
||||
|
||||
type tfoConn struct {
|
||||
net.Conn
|
||||
closed bool
|
||||
|
||||
11
component/dialer/tfo_windows.go
Normal file
11
component/dialer/tfo_windows.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package dialer
|
||||
|
||||
import "github.com/metacubex/mihomo/constant/features"
|
||||
|
||||
func init() {
|
||||
// According to MSDN, this option is available since Windows 10, 1607
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms738596(v=vs.85).aspx
|
||||
if features.WindowsMajorVersion < 10 || (features.WindowsMajorVersion == 10 && features.WindowsBuildNumber < 14393) {
|
||||
DisableTFO = true
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
|
||||
#ifndef __BPF_ENDIAN__
|
||||
#define __BPF_ENDIAN__
|
||||
|
||||
/*
|
||||
* Isolate byte #n and put it into byte #m, for __u##b type.
|
||||
* E.g., moving byte #6 (nnnnnnnn) into byte #1 (mmmmmmmm) for __u64:
|
||||
* 1) xxxxxxxx nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx
|
||||
* 2) nnnnnnnn xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx mmmmmmmm xxxxxxxx 00000000
|
||||
* 3) 00000000 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn
|
||||
* 4) 00000000 00000000 00000000 00000000 00000000 00000000 nnnnnnnn 00000000
|
||||
*/
|
||||
#define ___bpf_mvb(x, b, n, m) ((__u##b)(x) << (b-(n+1)*8) >> (b-8) << (m*8))
|
||||
|
||||
#define ___bpf_swab16(x) ((__u16)( \
|
||||
___bpf_mvb(x, 16, 0, 1) | \
|
||||
___bpf_mvb(x, 16, 1, 0)))
|
||||
|
||||
#define ___bpf_swab32(x) ((__u32)( \
|
||||
___bpf_mvb(x, 32, 0, 3) | \
|
||||
___bpf_mvb(x, 32, 1, 2) | \
|
||||
___bpf_mvb(x, 32, 2, 1) | \
|
||||
___bpf_mvb(x, 32, 3, 0)))
|
||||
|
||||
#define ___bpf_swab64(x) ((__u64)( \
|
||||
___bpf_mvb(x, 64, 0, 7) | \
|
||||
___bpf_mvb(x, 64, 1, 6) | \
|
||||
___bpf_mvb(x, 64, 2, 5) | \
|
||||
___bpf_mvb(x, 64, 3, 4) | \
|
||||
___bpf_mvb(x, 64, 4, 3) | \
|
||||
___bpf_mvb(x, 64, 5, 2) | \
|
||||
___bpf_mvb(x, 64, 6, 1) | \
|
||||
___bpf_mvb(x, 64, 7, 0)))
|
||||
|
||||
/* LLVM's BPF target selects the endianness of the CPU
|
||||
* it compiles on, or the user specifies (bpfel/bpfeb),
|
||||
* respectively. The used __BYTE_ORDER__ is defined by
|
||||
* the compiler, we cannot rely on __BYTE_ORDER from
|
||||
* libc headers, since it doesn't reflect the actual
|
||||
* requested byte order.
|
||||
*
|
||||
* Note, LLVM's BPF target has different __builtin_bswapX()
|
||||
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
|
||||
* in bpfel and bpfeb case, which means below, that we map
|
||||
* to cpu_to_be16(). We could use it unconditionally in BPF
|
||||
* case, but better not rely on it, so that this header here
|
||||
* can be used from application and BPF program side, which
|
||||
* use different targets.
|
||||
*/
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __bpf_ntohs(x) __builtin_bswap16(x)
|
||||
# define __bpf_htons(x) __builtin_bswap16(x)
|
||||
# define __bpf_constant_ntohs(x) ___bpf_swab16(x)
|
||||
# define __bpf_constant_htons(x) ___bpf_swab16(x)
|
||||
# define __bpf_ntohl(x) __builtin_bswap32(x)
|
||||
# define __bpf_htonl(x) __builtin_bswap32(x)
|
||||
# define __bpf_constant_ntohl(x) ___bpf_swab32(x)
|
||||
# define __bpf_constant_htonl(x) ___bpf_swab32(x)
|
||||
# define __bpf_be64_to_cpu(x) __builtin_bswap64(x)
|
||||
# define __bpf_cpu_to_be64(x) __builtin_bswap64(x)
|
||||
# define __bpf_constant_be64_to_cpu(x) ___bpf_swab64(x)
|
||||
# define __bpf_constant_cpu_to_be64(x) ___bpf_swab64(x)
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __bpf_ntohs(x) (x)
|
||||
# define __bpf_htons(x) (x)
|
||||
# define __bpf_constant_ntohs(x) (x)
|
||||
# define __bpf_constant_htons(x) (x)
|
||||
# define __bpf_ntohl(x) (x)
|
||||
# define __bpf_htonl(x) (x)
|
||||
# define __bpf_constant_ntohl(x) (x)
|
||||
# define __bpf_constant_htonl(x) (x)
|
||||
# define __bpf_be64_to_cpu(x) (x)
|
||||
# define __bpf_cpu_to_be64(x) (x)
|
||||
# define __bpf_constant_be64_to_cpu(x) (x)
|
||||
# define __bpf_constant_cpu_to_be64(x) (x)
|
||||
#else
|
||||
# error "Fix your compiler's __BYTE_ORDER__?!"
|
||||
#endif
|
||||
|
||||
#define bpf_htons(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_htons(x) : __bpf_htons(x))
|
||||
#define bpf_ntohs(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
|
||||
#define bpf_htonl(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_htonl(x) : __bpf_htonl(x))
|
||||
#define bpf_ntohl(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
|
||||
#define bpf_cpu_to_be64(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_cpu_to_be64(x) : __bpf_cpu_to_be64(x))
|
||||
#define bpf_be64_to_cpu(x) \
|
||||
(__builtin_constant_p(x) ? \
|
||||
__bpf_constant_be64_to_cpu(x) : __bpf_be64_to_cpu(x))
|
||||
|
||||
#endif /* __BPF_ENDIAN__ */
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user