mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-03 04:09:53 +00:00
Compare commits
100 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 |
16
.github/mihomo.service
vendored
16
.github/mihomo.service
vendored
@@ -1,17 +1,17 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=mihomo Daemon, Another Clash Kernel.
|
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]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
LimitNPROC=500
|
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
LimitNOFILE=1000000
|
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
|
||||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
|
||||||
Restart=always
|
|
||||||
ExecStartPre=/usr/bin/sleep 2s
|
|
||||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||||
ExecReload=/bin/kill -HUP $MAINPID
|
ExecReload=/bin/kill -HUP $MAINPID
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=10
|
||||||
|
LimitNOFILE=infinity
|
||||||
|
|
||||||
[Install]
|
[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
|
||||||
62
.github/workflows/build.yml
vendored
62
.github/workflows/build.yml
vendored
@@ -54,7 +54,6 @@ jobs:
|
|||||||
- { goos: windows, goarch: '386', output: '386' }
|
- { goos: windows, goarch: '386', output: '386' }
|
||||||
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
|
||||||
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
|
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
|
||||||
- { goos: windows, goarch: arm, goarm: '7', output: armv7 }
|
|
||||||
- { goos: windows, goarch: arm64, output: arm64 }
|
- { goos: windows, goarch: arm64, output: arm64 }
|
||||||
|
|
||||||
- { goos: freebsd, goarch: '386', output: '386' }
|
- { goos: freebsd, goarch: '386', output: '386' }
|
||||||
@@ -67,6 +66,12 @@ jobs:
|
|||||||
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
|
||||||
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
|
- { 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
|
# Go 1.22 with special patch can work on Windows 7
|
||||||
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||||
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
|
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
|
||||||
@@ -95,6 +100,11 @@ jobs:
|
|||||||
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
|
||||||
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
|
- { goos: darwin, goarch: amd64, goamd64: 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
|
# only for test
|
||||||
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
|
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
|
||||||
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
|
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
|
||||||
@@ -104,30 +114,41 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Go
|
- 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
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.23'
|
go-version: '1.24'
|
||||||
|
|
||||||
- name: Set up Go
|
- 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
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.jobs.goversion }}
|
go-version: ${{ matrix.jobs.goversion }}
|
||||||
|
|
||||||
- name: Set up Go1.22 loongarch abi1
|
- name: Set up Go1.23 loongarch abi1
|
||||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
|
||||||
run: |
|
run: |
|
||||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz
|
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.22.4.linux-amd64-abi1.tar.gz -C /usr/local
|
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Set up Go1.22 loongarch abi2
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
|
# 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: |
|
run: |
|
||||||
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz
|
cd $(go env GOROOT)
|
||||||
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local
|
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
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
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
# this patch file only works on golang1.23.x
|
# this patch file only works on golang1.23.x
|
||||||
@@ -139,7 +160,7 @@ jobs:
|
|||||||
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
- name: Revert Golang1.23 commit for Windows7/8
|
- name: Revert Golang1.23 commit for Windows7/8
|
||||||
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
|
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
|
||||||
run: |
|
run: |
|
||||||
cd $(go env GOROOT)
|
cd $(go env GOROOT)
|
||||||
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||||
@@ -194,7 +215,7 @@ jobs:
|
|||||||
uses: nttld/setup-ndk@v1
|
uses: nttld/setup-ndk@v1
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
with:
|
with:
|
||||||
ndk-version: r27
|
ndk-version: r28-beta1
|
||||||
|
|
||||||
- name: Set NDK path
|
- name: Set NDK path
|
||||||
if: ${{ matrix.jobs.goos == 'android' }}
|
if: ${{ matrix.jobs.goos == 'android' }}
|
||||||
@@ -255,17 +276,20 @@ jobs:
|
|||||||
|
|
||||||
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN
|
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}/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}/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 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
|
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
|
||||||
mixed-port: 7890
|
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
|
EOF
|
||||||
|
|
||||||
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF
|
||||||
|
|||||||
@@ -98,3 +98,4 @@ API.
|
|||||||
|
|
||||||
This software is released under the GPL-3.0 license.
|
This software is released under the GPL-3.0 license.
|
||||||
|
|
||||||
|
**In addition, any downstream projects not affiliated with `MetaCubeX` shall not contain the word `mihomo` in their names.**
|
||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/atomic"
|
"github.com/metacubex/mihomo/common/atomic"
|
||||||
@@ -18,6 +19,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -161,8 +163,17 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
|
|||||||
mapping["alive"] = p.alive.Load()
|
mapping["alive"] = p.alive.Load()
|
||||||
mapping["name"] = p.Name()
|
mapping["name"] = p.Name()
|
||||||
mapping["udp"] = p.SupportUDP()
|
mapping["udp"] = p.SupportUDP()
|
||||||
mapping["xudp"] = p.SupportXUDP()
|
mapping["uot"] = p.SupportUOT()
|
||||||
mapping["tfo"] = p.SupportTFO()
|
|
||||||
|
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)
|
return json.Marshal(mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,10 +271,18 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
|||||||
|
|
||||||
if unifiedDelay {
|
if unifiedDelay {
|
||||||
second := time.Now()
|
second := time.Now()
|
||||||
resp, err = client.Do(req)
|
var ignoredErr error
|
||||||
if err == nil {
|
var secondResp *http.Response
|
||||||
|
secondResp, ignoredErr = client.Do(req)
|
||||||
|
if ignoredErr == nil {
|
||||||
|
resp = secondResp
|
||||||
_ = resp.Body.Close()
|
_ = resp.Body.Close()
|
||||||
start = second
|
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.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,12 +34,5 @@ func SkipAuthRemoteAddress(addr string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func skipAuth(addr netip.Addr) bool {
|
func skipAuth(addr netip.Addr) bool {
|
||||||
if addr.IsValid() {
|
return prefixesContains(skipAuthPrefixes, addr)
|
||||||
for _, prefix := range skipAuthPrefixes {
|
|
||||||
if prefix.Contains(addr.Unmap()) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,27 +31,17 @@ func IsRemoteAddrDisAllowed(addr net.Addr) bool {
|
|||||||
if err := m.SetRemoteAddr(addr); err != nil {
|
if err := m.SetRemoteAddr(addr); err != nil {
|
||||||
return false
|
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 {
|
func isAllowed(addr netip.Addr) bool {
|
||||||
if addr.IsValid() {
|
return prefixesContains(lanAllowedIPs, addr)
|
||||||
for _, prefix := range lanAllowedIPs {
|
|
||||||
if prefix.Contains(addr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isDisAllowed(addr netip.Addr) bool {
|
func isDisAllowed(addr netip.Addr) bool {
|
||||||
if addr.IsValid() {
|
return prefixesContains(lanDisAllowedIPs, addr)
|
||||||
for _, prefix := range lanDisAllowedIPs {
|
|
||||||
if prefix.Contains(addr) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package inbound
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/netip"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/keepalive"
|
"github.com/metacubex/mihomo/component/keepalive"
|
||||||
@@ -41,7 +43,36 @@ func MPTCP() bool {
|
|||||||
return getMultiPathTCP(&lc.ListenConfig)
|
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) {
|
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||||
|
address, err := preResolve(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
mutex.RLock()
|
mutex.RLock()
|
||||||
defer mutex.RUnlock()
|
defer mutex.RUnlock()
|
||||||
return lc.Listen(ctx, network, address)
|
return lc.Listen(ctx, network, address)
|
||||||
@@ -51,6 +82,21 @@ func Listen(network, address string) (net.Listener, error) {
|
|||||||
return ListenContext(context.Background(), network, address)
|
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() {
|
func init() {
|
||||||
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
|
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
|
|||||||
@@ -61,3 +61,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
|
|||||||
|
|
||||||
return 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
|
return b.udp
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportXUDP implements C.ProxyAdapter
|
// ProxyInfo implements C.ProxyAdapter
|
||||||
func (b *Base) SupportXUDP() bool {
|
func (b *Base) ProxyInfo() (info C.ProxyInfo) {
|
||||||
return b.xudp
|
info.XUDP = b.xudp
|
||||||
}
|
info.TFO = b.tfo
|
||||||
|
info.MPTCP = b.mpTcp
|
||||||
// SupportTFO implements C.ProxyAdapter
|
info.SMUX = false
|
||||||
func (b *Base) SupportTFO() bool {
|
info.Interface = b.iface
|
||||||
return b.tfo
|
info.RoutingMark = b.rmark
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsL3Protocol implements C.ProxyAdapter
|
// IsL3Protocol implements C.ProxyAdapter
|
||||||
@@ -152,11 +153,11 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type BasicOption struct {
|
type BasicOption struct {
|
||||||
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
|
TFO bool `proxy:"tfo,omitempty"`
|
||||||
MPTCP bool `proxy:"mptcp,omitempty" group:"mptcp,omitempty"`
|
MPTCP bool `proxy:"mptcp,omitempty"`
|
||||||
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
|
||||||
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,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
|
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,12 @@ package outbound
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/loopback"
|
"github.com/metacubex/mihomo/component/loopback"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/constant/features"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
|
||||||
|
|
||||||
type Direct struct {
|
type Direct struct {
|
||||||
*Base
|
*Base
|
||||||
loopBack *loopback.Detector
|
loopBack *loopback.Detector
|
||||||
@@ -27,12 +21,10 @@ type DirectOption struct {
|
|||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
if !features.CMFA && !DisableLoopBackDetector {
|
if err := d.loopBack.CheckConn(metadata); err != nil {
|
||||||
if err := d.loopBack.CheckConn(metadata); err != nil {
|
return nil, err
|
||||||
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...)...)
|
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -42,14 +34,12 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||||||
|
|
||||||
// ListenPacketContext implements C.ProxyAdapter
|
// ListenPacketContext implements C.ProxyAdapter
|
||||||
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (d *Direct) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
if !features.CMFA && !DisableLoopBackDetector {
|
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
||||||
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
|
return nil, err
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
|
||||||
if !metadata.Resolved() {
|
if !metadata.Resolved() {
|
||||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DefaultResolver)
|
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, resolver.DirectHostResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("can't resolve ip")
|
return nil, errors.New("can't resolve ip")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,13 @@ func (h *Http) SupportWithDialer() C.NetWork {
|
|||||||
return C.TCP
|
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 {
|
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
addr := metadata.RemoteAddress()
|
addr := metadata.RemoteAddress()
|
||||||
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
|||||||
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
||||||
return &hyDialerWithContext{
|
return &hyDialerWithContext{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
hyDialer: func(network string) (net.PacketConn, error) {
|
hyDialer: func(network string, rAddr net.Addr) (net.PacketConn, error) {
|
||||||
var err error
|
var err error
|
||||||
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
|
var cDialer C.Dialer = dialer.NewDialer(h.Base.DialOptions(opts...)...)
|
||||||
if len(h.option.DialerProxy) > 0 {
|
if len(h.option.DialerProxy) > 0 {
|
||||||
@@ -78,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rAddrPort, _ := netip.ParseAddrPort(h.Addr())
|
rAddrPort, _ := netip.ParseAddrPort(rAddr.String())
|
||||||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||||
},
|
},
|
||||||
remoteAddr: func(addr string) (net.Addr, error) {
|
remoteAddr: func(addr string) (net.Addr, error) {
|
||||||
@@ -87,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 {
|
type HysteriaOption struct {
|
||||||
BasicOption
|
BasicOption
|
||||||
Name string `proxy:"name"`
|
Name string `proxy:"name"`
|
||||||
@@ -131,11 +138,7 @@ func (c *HysteriaOption) Speed() (uint64, uint64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||||
clientTransport := &transport.ClientTransport{
|
clientTransport := &transport.ClientTransport{}
|
||||||
Dialer: &net.Dialer{
|
|
||||||
Timeout: 8 * time.Second,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
ports := option.Ports
|
ports := option.Ports
|
||||||
|
|
||||||
@@ -284,7 +287,7 @@ func (c *hyPacketConn) WriteTo(p []byte, addr net.Addr) (n int, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type hyDialerWithContext struct {
|
type hyDialerWithContext struct {
|
||||||
hyDialer func(network string) (net.PacketConn, error)
|
hyDialer func(network string, rAddr net.Addr) (net.PacketConn, error)
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
remoteAddr func(host string) (net.Addr, error)
|
remoteAddr func(host string) (net.Addr, error)
|
||||||
}
|
}
|
||||||
@@ -294,7 +297,7 @@ func (h *hyDialerWithContext) ListenPacket(rAddr net.Addr) (net.PacketConn, erro
|
|||||||
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
|
if addrPort, err := netip.ParseAddrPort(rAddr.String()); err == nil {
|
||||||
network = dialer.ParseNetwork(network, addrPort.Addr())
|
network = dialer.ParseNetwork(network, addrPort.Addr())
|
||||||
}
|
}
|
||||||
return h.hyDialer(network)
|
return h.hyDialer(network, rAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *hyDialerWithContext) Context() context.Context {
|
func (h *hyDialerWithContext) Context() context.Context {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"github.com/metacubex/sing-quic/hysteria2"
|
"github.com/metacubex/sing-quic/hysteria2"
|
||||||
|
|
||||||
|
"github.com/metacubex/quic-go"
|
||||||
"github.com/metacubex/randv2"
|
"github.com/metacubex/randv2"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
)
|
)
|
||||||
@@ -62,6 +63,12 @@ type Hysteria2Option struct {
|
|||||||
CustomCAString string `proxy:"ca-str,omitempty"`
|
CustomCAString string `proxy:"ca-str,omitempty"`
|
||||||
CWND int `proxy:"cwnd,omitempty"`
|
CWND int `proxy:"cwnd,omitempty"`
|
||||||
UdpMTU int `proxy:"udp-mtu,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) {
|
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
@@ -96,6 +103,13 @@ func closeHysteria2(h *Hysteria2) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
var salamanderPassword string
|
var salamanderPassword string
|
||||||
@@ -138,6 +152,13 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||||||
option.UdpMTU = 1200 - 3
|
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())
|
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||||
|
|
||||||
clientOptions := hysteria2.ClientOptions{
|
clientOptions := hysteria2.ClientOptions{
|
||||||
@@ -149,6 +170,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||||||
SalamanderPassword: salamanderPassword,
|
SalamanderPassword: salamanderPassword,
|
||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
|
QUICConfig: quicConfig,
|
||||||
UDPDisabled: false,
|
UDPDisabled: false,
|
||||||
CWND: option.CWND,
|
CWND: option.CWND,
|
||||||
UdpMTU: option.UdpMTU,
|
UdpMTU: option.UdpMTU,
|
||||||
|
|||||||
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -196,6 +196,13 @@ func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
|
|||||||
return C.ALLNet
|
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
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
if ss.option.UDPOverTCP {
|
if ss.option.UDPOverTCP {
|
||||||
@@ -229,7 +236,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
|||||||
Password: option.Password,
|
Password: option.Password,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
var v2rayOption *v2rayObfs.Option
|
||||||
|
|||||||
@@ -122,6 +122,13 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
|
|||||||
return C.ALLNet
|
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) {
|
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
||||||
// SSR protocol compatibility
|
// SSR protocol compatibility
|
||||||
// https://github.com/metacubex/mihomo/pull/2056
|
// https://github.com/metacubex/mihomo/pull/2056
|
||||||
@@ -134,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
|
|||||||
password := option.Password
|
password := option.Password
|
||||||
coreCiph, err := core.PickCipher(cipher, nil, password)
|
coreCiph, err := core.PickCipher(cipher, nil, password)
|
||||||
if err != nil {
|
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 (
|
var (
|
||||||
ivSize int
|
ivSize int
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ func (s *SingMux) SupportUOT() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SingMux) ProxyInfo() C.ProxyInfo {
|
||||||
|
info := s.ProxyAdapter.ProxyInfo()
|
||||||
|
info.SMUX = true
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
func closeSingMux(s *SingMux) {
|
func closeSingMux(s *SingMux) {
|
||||||
_ = s.client.Close()
|
_ = s.client.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,13 @@ func (s *Snell) SupportUOT() bool {
|
|||||||
return true
|
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) {
|
func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
psk := []byte(option.Psk)
|
psk := []byte(option.Psk)
|
||||||
@@ -204,7 +211,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,13 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||||||
return newPacketConn(&socksPacketConn{PacketConn: pc, rAddr: bindUDPAddr, tcpConn: c}, ss), nil
|
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) {
|
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
|
|||||||
@@ -121,6 +121,13 @@ func closeSsh(s *Ssh) {
|
|||||||
_ = s.client.Close()
|
_ = 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) {
|
func NewSsh(option SshOption) (*Ssh, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
|
|||||||
@@ -244,6 +244,13 @@ func (t *Trojan) SupportUOT() bool {
|
|||||||
return true
|
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) {
|
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,13 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
|
|||||||
return
|
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) {
|
func NewTuic(option TuicOption) (*Tuic, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
serverName := option.Server
|
serverName := option.Server
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ip, err := resolver.ResolveProxyServerHost(ctx, host)
|
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -71,12 +71,12 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
|
|||||||
var fallback netip.Addr
|
var fallback netip.Addr
|
||||||
switch prefer {
|
switch prefer {
|
||||||
case C.IPv4Only:
|
case C.IPv4Only:
|
||||||
ip, err = resolver.ResolveIPv4ProxyServerHost(ctx, host)
|
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
case C.IPv6Only:
|
case C.IPv6Only:
|
||||||
ip, err = resolver.ResolveIPv6ProxyServerHost(ctx, host)
|
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
case C.IPv6Prefer:
|
case C.IPv6Prefer:
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, addr := range ips {
|
for _, addr := range ips {
|
||||||
if addr.Is6() {
|
if addr.Is6() {
|
||||||
@@ -92,7 +92,7 @@ func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, pref
|
|||||||
default:
|
default:
|
||||||
// C.IPv4Prefer, C.DualStack and other
|
// C.IPv4Prefer, C.DualStack and other
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, addr := range ips {
|
for _, addr := range ips {
|
||||||
if addr.Is4() {
|
if addr.Is4() {
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
"github.com/metacubex/mihomo/transport/gun"
|
"github.com/metacubex/mihomo/transport/gun"
|
||||||
"github.com/metacubex/mihomo/transport/socks5"
|
"github.com/metacubex/mihomo/transport/socks5"
|
||||||
"github.com/metacubex/mihomo/transport/vless"
|
"github.com/metacubex/mihomo/transport/vless"
|
||||||
@@ -379,6 +378,13 @@ func (v *Vless) SupportUOT() bool {
|
|||||||
return true
|
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 {
|
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
var addr []byte
|
var addr []byte
|
||||||
@@ -506,8 +512,6 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||||||
if option.Flow != vless.XRV {
|
if option.Flow != vless.XRV {
|
||||||
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
return nil, fmt.Errorf("unsupported xtls flow type: %s", option.Flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnln("To use %s, ensure your server is upgrade to Xray-core v1.8.0+", vless.XRV)
|
|
||||||
addons = &vless.Addons{
|
addons = &vless.Addons{
|
||||||
Flow: option.Flow,
|
Flow: option.Flow,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -388,6 +388,13 @@ func (v *Vmess) SupportWithDialer() C.NetWork {
|
|||||||
return C.ALLNet
|
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
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
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
|
// 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,
|
option: &option,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v.realityConfig, err = v.option.RealityOpts.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
switch option.Network {
|
switch option.Network {
|
||||||
case "h2":
|
case "h2":
|
||||||
if len(option.HTTP2Opts.Host) == 0 {
|
if len(option.HTTP2Opts.Host) == 0 {
|
||||||
@@ -500,11 +512,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||||||
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
|
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
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ type WireGuard struct {
|
|||||||
device wireguardGoDevice
|
device wireguardGoDevice
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
dialer proxydialer.SingDialer
|
dialer proxydialer.SingDialer
|
||||||
resolver *dns.Resolver
|
resolver resolver.Resolver
|
||||||
refP *refProxyAdapter
|
refP *refProxyAdapter
|
||||||
|
|
||||||
initOk atomic.Bool
|
initOk atomic.Bool
|
||||||
@@ -296,7 +296,7 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||||||
for i := range nss {
|
for i := range nss {
|
||||||
nss[i].ProxyAdapter = refP
|
nss[i].ProxyAdapter = refP
|
||||||
}
|
}
|
||||||
outbound.resolver, _ = dns.NewResolver(dns.Config{
|
outbound.resolver = dns.NewResolver(dns.Config{
|
||||||
Main: nss,
|
Main: nss,
|
||||||
IPv6: has6,
|
IPv6: has6,
|
||||||
})
|
})
|
||||||
@@ -611,18 +611,11 @@ func (r *refProxyAdapter) SupportUDP() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *refProxyAdapter) SupportXUDP() bool {
|
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
|
||||||
if r.proxyAdapter != nil {
|
if r.proxyAdapter != nil {
|
||||||
return r.proxyAdapter.SupportXUDP()
|
return r.proxyAdapter.ProxyInfo()
|
||||||
}
|
}
|
||||||
return false
|
return C.ProxyInfo{}
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) SupportTFO() bool {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.SupportTFO()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
|
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
|
||||||
|
|||||||
@@ -46,6 +46,13 @@ type GroupBaseOption struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
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
|
var excludeFilterReg *regexp2.Regexp
|
||||||
if opt.excludeFilter != "" {
|
if opt.excludeFilter != "" {
|
||||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
|
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
|
||||||
|
|||||||
@@ -204,7 +204,7 @@ func strategyStickySessions(url string) strategyFn {
|
|||||||
for i := 1; i < maxRetry; i++ {
|
for i := 1; i < maxRetry; i++ {
|
||||||
proxy := proxies[nowIdx]
|
proxy := proxies[nowIdx]
|
||||||
if proxy.AliveForTestUrl(url) {
|
if proxy.AliveForTestUrl(url) {
|
||||||
if nowIdx != idx {
|
if !has || nowIdx != idx {
|
||||||
lruCache.Set(key, nowIdx)
|
lruCache.Set(key, nowIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -141,6 +141,20 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewSsh(*sshOption)
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,15 +57,17 @@ type OverrideSchema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type proxyProviderSchema struct {
|
type proxyProviderSchema struct {
|
||||||
Type string `provider:"type"`
|
Type string `provider:"type"`
|
||||||
Path string `provider:"path,omitempty"`
|
Path string `provider:"path,omitempty"`
|
||||||
URL string `provider:"url,omitempty"`
|
URL string `provider:"url,omitempty"`
|
||||||
Proxy string `provider:"proxy,omitempty"`
|
Proxy string `provider:"proxy,omitempty"`
|
||||||
Interval int `provider:"interval,omitempty"`
|
Interval int `provider:"interval,omitempty"`
|
||||||
Filter string `provider:"filter,omitempty"`
|
Filter string `provider:"filter,omitempty"`
|
||||||
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
ExcludeFilter string `provider:"exclude-filter,omitempty"`
|
||||||
ExcludeType string `provider:"exclude-type,omitempty"`
|
ExcludeType string `provider:"exclude-type,omitempty"`
|
||||||
DialerProxy string `provider:"dialer-proxy,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"`
|
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
|
||||||
Override OverrideSchema `provider:"override,omitempty"`
|
Override OverrideSchema `provider:"override,omitempty"`
|
||||||
@@ -98,6 +100,11 @@ 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)
|
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
|
var vehicle types.Vehicle
|
||||||
switch schema.Type {
|
switch schema.Type {
|
||||||
case "file":
|
case "file":
|
||||||
@@ -111,17 +118,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
|||||||
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
return nil, fmt.Errorf("%w: %s", errSubPath, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout)
|
vehicle = resource.NewHTTPVehicle(schema.URL, path, schema.Proxy, schema.Header, resource.DefaultHttpTimeout, schema.SizeLimit)
|
||||||
|
case "inline":
|
||||||
|
return NewInlineProvider(name, schema.Payload, parser, hc)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
interval := time.Duration(uint(schema.Interval)) * time.Second
|
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,7 +1,6 @@
|
|||||||
package provider
|
package provider
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/adapter"
|
"github.com/metacubex/mihomo/adapter"
|
||||||
"github.com/metacubex/mihomo/common/convert"
|
"github.com/metacubex/mihomo/common/convert"
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
"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"
|
"github.com/metacubex/mihomo/component/resource"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
types "github.com/metacubex/mihomo/constant/provider"
|
types "github.com/metacubex/mihomo/constant/provider"
|
||||||
@@ -32,44 +31,101 @@ type ProxySchema struct {
|
|||||||
Proxies []map[string]any `yaml:"proxies"`
|
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
|
// ProxySetProvider for auto gc
|
||||||
type ProxySetProvider struct {
|
type ProxySetProvider struct {
|
||||||
*proxySetProvider
|
*proxySetProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
type proxySetProvider struct {
|
type proxySetProvider struct {
|
||||||
|
baseProvider
|
||||||
*resource.Fetcher[[]C.Proxy]
|
*resource.Fetcher[[]C.Proxy]
|
||||||
proxies []C.Proxy
|
|
||||||
healthCheck *HealthCheck
|
|
||||||
version uint32
|
|
||||||
subscriptionInfo *SubscriptionInfo
|
subscriptionInfo *SubscriptionInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(map[string]any{
|
return json.Marshal(providerForApi{
|
||||||
"name": pp.Name(),
|
Name: pp.Name(),
|
||||||
"type": pp.Type().String(),
|
Type: pp.Type().String(),
|
||||||
"vehicleType": pp.VehicleType().String(),
|
VehicleType: pp.VehicleType().String(),
|
||||||
"proxies": pp.Proxies(),
|
Proxies: pp.Proxies(),
|
||||||
"testUrl": pp.healthCheck.url,
|
TestUrl: pp.healthCheck.url,
|
||||||
"expectedStatus": pp.healthCheck.expectedStatus.String(),
|
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
|
||||||
"updatedAt": pp.UpdatedAt(),
|
UpdatedAt: pp.UpdatedAt(),
|
||||||
"subscriptionInfo": pp.subscriptionInfo,
|
SubscriptionInfo: pp.subscriptionInfo,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Version() uint32 {
|
|
||||||
return pp.version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) Name() string {
|
func (pp *proxySetProvider) Name() string {
|
||||||
return pp.Fetcher.Name()
|
return pp.Fetcher.Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) HealthCheck() {
|
|
||||||
pp.healthCheck.check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) Update() error {
|
func (pp *proxySetProvider) Update() error {
|
||||||
_, _, err := pp.Fetcher.Update()
|
_, _, err := pp.Fetcher.Update()
|
||||||
return err
|
return err
|
||||||
@@ -80,74 +136,13 @@ func (pp *proxySetProvider) Initial() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
pp.getSubscriptionInfo()
|
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
|
||||||
|
pp.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
|
||||||
|
}
|
||||||
pp.closeAllConnections()
|
pp.closeAllConnections()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Type() types.ProviderType {
|
|
||||||
return types.Proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) Proxies() []C.Proxy {
|
|
||||||
return pp.proxies
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) Count() int {
|
|
||||||
return len(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.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
|
|
||||||
http.MethodGet, nil, nil, pp.Vehicle().Proxy())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
userInfoStr := strings.TrimSpace(resp.Header.Get("subscription-userinfo"))
|
|
||||||
if userInfoStr == "" {
|
|
||||||
resp2, err := mihomoHttp.HttpRequestWithProxy(ctx, pp.Vehicle().Url(),
|
|
||||||
http.MethodGet, http.Header{"User-Agent": {"Quantumultx"}}, nil, pp.Vehicle().Proxy())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp2.Body.Close()
|
|
||||||
userInfoStr = strings.TrimSpace(resp2.Header.Get("subscription-userinfo"))
|
|
||||||
if userInfoStr == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pp.subscriptionInfo = NewSubscriptionInfo(userInfoStr)
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *proxySetProvider) closeAllConnections() {
|
func (pp *proxySetProvider) closeAllConnections() {
|
||||||
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
|
||||||
for _, chain := range c.Chains() {
|
for _, chain := range c.Chains() {
|
||||||
@@ -161,11 +156,183 @@ func (pp *proxySetProvider) closeAllConnections() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (pp *proxySetProvider) Close() error {
|
func (pp *proxySetProvider) Close() error {
|
||||||
pp.healthCheck.close()
|
_ = pp.baseProvider.Close()
|
||||||
return pp.Fetcher.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) {
|
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{
|
||||||
|
baseProvider: baseProvider{
|
||||||
|
name: name,
|
||||||
|
proxies: []C.Proxy{},
|
||||||
|
healthCheck: hc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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, (*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 {
|
||||||
|
baseProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||||
|
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) Update() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) Initial() error {
|
||||||
|
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
||||||
|
cp.HealthCheck()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||||
|
return types.Compatible
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
if hc.auto() {
|
||||||
|
go hc.process()
|
||||||
|
}
|
||||||
|
|
||||||
|
pd := &compatibleProvider{
|
||||||
|
baseProvider: baseProvider{
|
||||||
|
name: name,
|
||||||
|
proxies: proxies,
|
||||||
|
healthCheck: hc,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper := &CompatibleProvider{pd}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
|
||||||
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
|
||||||
@@ -184,140 +351,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
|
|||||||
filterRegs = append(filterRegs, filterReg)
|
filterRegs = append(filterRegs, filterReg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hc.auto() {
|
|
||||||
go hc.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &proxySetProvider{
|
|
||||||
proxies: []C.Proxy{},
|
|
||||||
healthCheck: hc,
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
|
|
||||||
pd.Fetcher = fetcher
|
|
||||||
wrapper := &ProxySetProvider{pd}
|
|
||||||
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
|
|
||||||
return wrapper, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pp *ProxySetProvider) Close() error {
|
|
||||||
runtime.SetFinalizer(pp, nil)
|
|
||||||
return pp.proxySetProvider.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompatibleProvider for auto gc
|
|
||||||
type CompatibleProvider struct {
|
|
||||||
*compatibleProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type compatibleProvider struct {
|
|
||||||
name string
|
|
||||||
healthCheck *HealthCheck
|
|
||||||
proxies []C.Proxy
|
|
||||||
version uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
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(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *compatibleProvider) Initial() error {
|
|
||||||
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
|
||||||
cp.HealthCheck()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Count() int {
|
|
||||||
return len(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 (cp *compatibleProvider) Close() error {
|
|
||||||
cp.healthCheck.close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
if hc.auto() {
|
|
||||||
go hc.process()
|
|
||||||
}
|
|
||||||
|
|
||||||
pd := &compatibleProvider{
|
|
||||||
name: name,
|
|
||||||
proxies: proxies,
|
|
||||||
healthCheck: hc,
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapper := &CompatibleProvider{pd}
|
|
||||||
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] {
|
|
||||||
return func(buf []byte) ([]C.Proxy, error) {
|
return func(buf []byte) ([]C.Proxy, error) {
|
||||||
schema := &ProxySchema{}
|
schema := &ProxySchema{}
|
||||||
|
|
||||||
@@ -432,5 +465,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
|||||||
}
|
}
|
||||||
|
|
||||||
return proxies, nil
|
return proxies, nil
|
||||||
}
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ type SubscriptionInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||||
userinfo = strings.ToLower(userinfo)
|
userinfo = strings.ReplaceAll(strings.ToLower(userinfo), " ", "")
|
||||||
userinfo = strings.ReplaceAll(userinfo, " ", "")
|
|
||||||
si = new(SubscriptionInfo)
|
si = new(SubscriptionInfo)
|
||||||
|
|
||||||
for _, field := range strings.Split(userinfo, ";") {
|
for _, field := range strings.Split(userinfo, ";") {
|
||||||
@@ -43,7 +42,6 @@ func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
|||||||
si.Expire = intValue
|
si.Expire = intValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return si
|
return si
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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
|
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
|
||||||
|
|
||||||
package dialer
|
package sockopt
|
||||||
|
|
||||||
import (
|
func reuseControl(fd uintptr) error { return nil }
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
func addrReuseToListenConfig(*net.ListenConfig) {}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
@@ -86,7 +86,27 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isNil returns true if the input is nil or a typed nil pointer.
|
||||||
|
func isNil(input any) bool {
|
||||||
|
if input == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(input)
|
||||||
|
return val.Kind() == reflect.Pointer && val.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Decoder) decode(name string, data any, val reflect.Value) error {
|
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 {
|
for {
|
||||||
kind := val.Kind()
|
kind := val.Kind()
|
||||||
if kind == reflect.Pointer && val.IsNil() {
|
if kind == reflect.Pointer && val.IsNil() {
|
||||||
|
|||||||
@@ -267,3 +267,21 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
|
|||||||
err = decoder.Decode(rawMap, s)
|
err = decoder.Decode(rawMap, s)
|
||||||
assert.NotNilf(t, err, "should throw error: %#v", 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, "")
|
||||||
|
}
|
||||||
|
|||||||
@@ -139,10 +139,13 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, r := range ranges {
|
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) {
|
if !f(i) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if i+1 < i { // integer overflow
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
var trustCerts []*x509.Certificate
|
|
||||||
var globalCertPool *x509.CertPool
|
var globalCertPool *x509.CertPool
|
||||||
var mutex sync.RWMutex
|
var mutex sync.RWMutex
|
||||||
var errNotMatch = errors.New("certificate fingerprints do not match")
|
var errNotMatch = errors.New("certificate fingerprints do not match")
|
||||||
@@ -30,11 +29,19 @@ var DisableSystemCa, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_CA"))
|
|||||||
func AddCertificate(certificate string) error {
|
func AddCertificate(certificate string) error {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
|
|
||||||
if certificate == "" {
|
if certificate == "" {
|
||||||
return fmt.Errorf("certificate is empty")
|
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
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("add certificate failed")
|
return fmt.Errorf("add certificate failed")
|
||||||
@@ -51,9 +58,6 @@ func initializeCertPool() {
|
|||||||
globalCertPool = x509.NewCertPool()
|
globalCertPool = x509.NewCertPool()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, cert := range trustCerts {
|
|
||||||
globalCertPool.AddCert(cert)
|
|
||||||
}
|
|
||||||
if !DisableEmbedCa {
|
if !DisableEmbedCa {
|
||||||
globalCertPool.AppendCertsFromPEM(_CaCertificates)
|
globalCertPool.AppendCertsFromPEM(_CaCertificates)
|
||||||
}
|
}
|
||||||
@@ -62,7 +66,6 @@ func initializeCertPool() {
|
|||||||
func ResetCertificate() {
|
func ResetCertificate() {
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
trustCerts = nil
|
|
||||||
initializeCertPool()
|
initializeCertPool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -340,26 +340,18 @@ func parseAddr(ctx context.Context, network, address string, preferResolver reso
|
|||||||
return nil, "-1", err
|
return nil, "-1", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if preferResolver == nil {
|
||||||
|
preferResolver = resolver.ProxyServerHostResolver
|
||||||
|
}
|
||||||
|
|
||||||
var ips []netip.Addr
|
var ips []netip.Addr
|
||||||
switch network {
|
switch network {
|
||||||
case "tcp4", "udp4":
|
case "tcp4", "udp4":
|
||||||
if preferResolver == nil {
|
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
||||||
ips, err = resolver.LookupIPv4ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv4WithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
case "tcp6", "udp6":
|
case "tcp6", "udp6":
|
||||||
if preferResolver == nil {
|
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
||||||
ips, err = resolver.LookupIPv6ProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPv6WithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
if preferResolver == nil {
|
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
||||||
ips, err = resolver.LookupIPProxyServerHost(ctx, host)
|
|
||||||
} else {
|
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, preferResolver)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
return nil, "-1", fmt.Errorf("dns resolve failed: %w", err)
|
||||||
|
|||||||
@@ -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"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"github.com/metacubex/mihomo/common/sockopt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||||
return c.Control(func(fd uintptr) {
|
return sockopt.RawConnReuseaddr(c)
|
||||||
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
37
component/generater/cmd.go
Normal file
37
component/generater/cmd.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package generater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gofrs/uuid/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Main(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
panic("Using: generate uuid/reality-keypair/wg-keypair")
|
||||||
|
}
|
||||||
|
switch args[0] {
|
||||||
|
case "uuid":
|
||||||
|
newUUID, err := uuid.NewV4()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(newUUID.String())
|
||||||
|
case "reality-keypair":
|
||||||
|
privateKey, err := GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]))
|
||||||
|
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]))
|
||||||
|
case "wg-keypair":
|
||||||
|
privateKey, err := GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println("PrivateKey: " + privateKey.String())
|
||||||
|
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
97
component/generater/types.go
Normal file
97
component/generater/types.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155
|
||||||
|
|
||||||
|
package generater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/curve25519"
|
||||||
|
)
|
||||||
|
|
||||||
|
// KeyLen is the expected key length for a WireGuard key.
|
||||||
|
const KeyLen = 32 // wgh.KeyLen
|
||||||
|
|
||||||
|
// A Key is a public, private, or pre-shared secret key. The Key constructor
|
||||||
|
// functions in this package can be used to create Keys suitable for each of
|
||||||
|
// these applications.
|
||||||
|
type Key [KeyLen]byte
|
||||||
|
|
||||||
|
// GenerateKey generates a Key suitable for use as a pre-shared secret key from
|
||||||
|
// a cryptographically safe source.
|
||||||
|
//
|
||||||
|
// The output Key should not be used as a private key; use GeneratePrivateKey
|
||||||
|
// instead.
|
||||||
|
func GenerateKey() (Key, error) {
|
||||||
|
b := make([]byte, KeyLen)
|
||||||
|
if _, err := rand.Read(b); err != nil {
|
||||||
|
return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewKey(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GeneratePrivateKey generates a Key suitable for use as a private key from a
|
||||||
|
// cryptographically safe source.
|
||||||
|
func GeneratePrivateKey() (Key, error) {
|
||||||
|
key, err := GenerateKey()
|
||||||
|
if err != nil {
|
||||||
|
return Key{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify random bytes using algorithm described at:
|
||||||
|
// https://cr.yp.to/ecdh.html.
|
||||||
|
key[0] &= 248
|
||||||
|
key[31] &= 127
|
||||||
|
key[31] |= 64
|
||||||
|
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewKey creates a Key from an existing byte slice. The byte slice must be
|
||||||
|
// exactly 32 bytes in length.
|
||||||
|
func NewKey(b []byte) (Key, error) {
|
||||||
|
if len(b) != KeyLen {
|
||||||
|
return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
var k Key
|
||||||
|
copy(k[:], b)
|
||||||
|
|
||||||
|
return k, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseKey parses a Key from a base64-encoded string, as produced by the
|
||||||
|
// Key.String method.
|
||||||
|
func ParseKey(s string) (Key, error) {
|
||||||
|
b, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewKey(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey computes a public key from the private key k.
|
||||||
|
//
|
||||||
|
// PublicKey should only be called when k is a private key.
|
||||||
|
func (k Key) PublicKey() Key {
|
||||||
|
var (
|
||||||
|
pub [KeyLen]byte
|
||||||
|
priv = [KeyLen]byte(k)
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
|
||||||
|
// so no need to specify it.
|
||||||
|
curve25519.ScalarBaseMult(&pub, &priv)
|
||||||
|
|
||||||
|
return Key(pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the base64-encoded string representation of a Key.
|
||||||
|
//
|
||||||
|
// ParseKey can be used to produce a new Key from this string.
|
||||||
|
func (k Key) String() string {
|
||||||
|
return base64.StdEncoding.EncodeToString(k[:])
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/listener/inner"
|
"github.com/metacubex/mihomo/listener/inner"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -71,8 +72,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
|||||||
if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
|
if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
} else {
|
} else {
|
||||||
d := net.Dialer{}
|
return dialer.DialContext(ctx, network, address)
|
||||||
return d.DialContext(ctx, network, address)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
TLSClientConfig: ca.GetGlobalTLSConfig(&tls.Config{}),
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ type Interface struct {
|
|||||||
Name string
|
Name string
|
||||||
Addresses []netip.Prefix
|
Addresses []netip.Prefix
|
||||||
HardwareAddr net.HardwareAddr
|
HardwareAddr net.HardwareAddr
|
||||||
|
Flags net.Flags
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -66,6 +67,7 @@ func Interfaces() (map[string]*Interface, error) {
|
|||||||
Name: iface.Name,
|
Name: iface.Name,
|
||||||
Addresses: ipNets,
|
Addresses: ipNets,
|
||||||
HardwareAddr: iface.HardwareAddr,
|
HardwareAddr: iface.HardwareAddr,
|
||||||
|
Flags: iface.Flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func KeepAliveInterval() time.Duration {
|
|||||||
|
|
||||||
func SetDisableKeepAlive(disable bool) {
|
func SetDisableKeepAlive(disable bool) {
|
||||||
if runtime.GOOS == "android" {
|
if runtime.GOOS == "android" {
|
||||||
setDisableKeepAlive(false)
|
setDisableKeepAlive(true)
|
||||||
} else {
|
} else {
|
||||||
setDisableKeepAlive(disable)
|
setDisableKeepAlive(disable)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,25 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/callback"
|
"github.com/metacubex/mihomo/common/callback"
|
||||||
"github.com/metacubex/mihomo/component/iface"
|
"github.com/metacubex/mihomo/component/iface"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/constant/features"
|
||||||
|
|
||||||
"github.com/puzpuzpuz/xsync/v3"
|
"github.com/puzpuzpuz/xsync/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if features.CMFA {
|
||||||
|
disableLoopBackDetector = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ErrReject = errors.New("reject loopback connection")
|
var ErrReject = errors.New("reject loopback connection")
|
||||||
|
|
||||||
type Detector struct {
|
type Detector struct {
|
||||||
@@ -20,6 +31,9 @@ type Detector struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDetector() *Detector {
|
func NewDetector() *Detector {
|
||||||
|
if disableLoopBackDetector {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return &Detector{
|
return &Detector{
|
||||||
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
|
||||||
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
|
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
|
||||||
@@ -27,6 +41,9 @@ func NewDetector() *Detector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
||||||
|
if l == nil || l.connMap == nil {
|
||||||
|
return conn
|
||||||
|
}
|
||||||
metadata := C.Metadata{}
|
metadata := C.Metadata{}
|
||||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||||
return conn
|
return conn
|
||||||
@@ -42,6 +59,9 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
||||||
|
if l == nil || l.packetConnMap == nil {
|
||||||
|
return conn
|
||||||
|
}
|
||||||
metadata := C.Metadata{}
|
metadata := C.Metadata{}
|
||||||
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
|
||||||
return conn
|
return conn
|
||||||
@@ -58,6 +78,9 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
||||||
|
if l == nil || l.connMap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
connAddr := metadata.SourceAddrPort()
|
connAddr := metadata.SourceAddrPort()
|
||||||
if !connAddr.IsValid() {
|
if !connAddr.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
@@ -69,6 +92,9 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
|
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
|
||||||
|
if l == nil || l.packetConnMap == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
connAddr := metadata.SourceAddrPort()
|
connAddr := metadata.SourceAddrPort()
|
||||||
if !connAddr.IsValid() {
|
if !connAddr.IsValid() {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/oschwald/maxminddb-golang"
|
"github.com/oschwald/maxminddb-golang"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,11 +24,16 @@ type ASNReader struct {
|
|||||||
*maxminddb.Reader
|
*maxminddb.Reader
|
||||||
}
|
}
|
||||||
|
|
||||||
type ASNResult struct {
|
type GeoLite2 struct {
|
||||||
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
|
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
|
||||||
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
|
AutonomousSystemOrganization string `maxminddb:"autonomous_system_organization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IPInfo struct {
|
||||||
|
ASN string `maxminddb:"asn"`
|
||||||
|
Name string `maxminddb:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
func (r IPReader) LookupCode(ipAddress net.IP) []string {
|
func (r IPReader) LookupCode(ipAddress net.IP) []string {
|
||||||
switch r.databaseType {
|
switch r.databaseType {
|
||||||
case typeMaxmind:
|
case typeMaxmind:
|
||||||
@@ -66,8 +72,18 @@ func (r IPReader) LookupCode(ipAddress net.IP) []string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r ASNReader) LookupASN(ip net.IP) ASNResult {
|
func (r ASNReader) LookupASN(ip net.IP) (string, string) {
|
||||||
var result ASNResult
|
switch r.Metadata.DatabaseType {
|
||||||
r.Lookup(ip, &result)
|
case "GeoLite2-ASN", "DBIP-ASN-Lite (compat=GeoLite2-ASN)":
|
||||||
return result
|
var result GeoLite2
|
||||||
|
_ = r.Lookup(ip, &result)
|
||||||
|
return fmt.Sprint(result.AutonomousSystemNumber), result.AutonomousSystemOrganization
|
||||||
|
case "ipinfo generic_asn_free.mmdb":
|
||||||
|
var result IPInfo
|
||||||
|
_ = r.Lookup(ip, &result)
|
||||||
|
return result.ASN[2:], result.Name
|
||||||
|
default:
|
||||||
|
log.Warnln("Unsupported ASN type: %s", r.Metadata.DatabaseType)
|
||||||
|
}
|
||||||
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
|
|||||||
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
|
||||||
for size, buf := uint32(8), make([]byte, 8); ; {
|
for size, buf := uint32(8), make([]byte, 8); ; {
|
||||||
ptr := unsafe.Pointer(&buf[0])
|
ptr := unsafe.Pointer(&buf[0])
|
||||||
err, _, _ := syscall.SyscallN(fn, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
err, _, _ := syscall.Syscall6(fn, 6, uintptr(ptr), uintptr(unsafe.Pointer(&size)), 0, uintptr(family), uintptr(class), 0)
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -215,13 +215,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
|
|||||||
|
|
||||||
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
buf := make([]uint16, syscall.MAX_LONG_PATH)
|
||||||
size := uint32(len(buf))
|
size := uint32(len(buf))
|
||||||
r1, _, err := syscall.SyscallN(
|
r1, _, err := syscall.Syscall6(
|
||||||
queryProcName,
|
queryProcName, 4,
|
||||||
uintptr(h),
|
uintptr(h),
|
||||||
uintptr(0),
|
uintptr(0),
|
||||||
uintptr(unsafe.Pointer(&buf[0])),
|
uintptr(unsafe.Pointer(&buf[0])),
|
||||||
uintptr(unsafe.Pointer(&size)),
|
uintptr(unsafe.Pointer(&size)),
|
||||||
)
|
0, 0)
|
||||||
if r1 == 0 {
|
if r1 == 0 {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,10 @@ var (
|
|||||||
fileMode os.FileMode = 0o666
|
fileMode os.FileMode = 0o666
|
||||||
defaultCache *CacheFile
|
defaultCache *CacheFile
|
||||||
|
|
||||||
bucketSelected = []byte("selected")
|
bucketSelected = []byte("selected")
|
||||||
bucketFakeip = []byte("fakeip")
|
bucketFakeip = []byte("fakeip")
|
||||||
bucketETag = []byte("etag")
|
bucketETag = []byte("etag")
|
||||||
|
bucketSubscriptionInfo = []byte("subscriptioninfo")
|
||||||
)
|
)
|
||||||
|
|
||||||
// CacheFile store and update the cache file
|
// CacheFile store and update the cache file
|
||||||
|
|||||||
41
component/profile/cachefile/subscriptioninfo.go
Normal file
41
component/profile/cachefile/subscriptioninfo.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package cachefile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
|
"github.com/metacubex/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *CacheFile) SetSubscriptionInfo(name string, userInfo string) {
|
||||||
|
if c.DB == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.DB.Batch(func(t *bbolt.Tx) error {
|
||||||
|
bucket, err := t.CreateBucketIfNotExists(bucketSubscriptionInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.Put([]byte(name), []byte(userInfo))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Warnln("[CacheFile] write cache to %s failed: %s", c.DB.Path(), err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (c *CacheFile) GetSubscriptionInfo(name string) (userInfo string) {
|
||||||
|
if c.DB == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.DB.View(func(t *bbolt.Tx) error {
|
||||||
|
if bucket := t.Bucket(bucketSubscriptionInfo); bucket != nil {
|
||||||
|
if v := bucket.Get([]byte(name)); v != nil {
|
||||||
|
userInfo = string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
|
||||||
|
|
||||||
package resolver
|
|
||||||
|
|
||||||
import _ "unsafe"
|
|
||||||
|
|
||||||
//go:linkname defaultNS net.defaultNS
|
|
||||||
var defaultNS []string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
defaultNS = []string{"114.114.114.114:53", "8.8.8.8:53"}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
//go:build !go1.22
|
|
||||||
|
|
||||||
// a simple standard lib fix from: https://github.com/golang/go/commit/33d4a5105cf2b2d549922e909e9239a48b8cefcc
|
|
||||||
|
|
||||||
package resolver
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
_ "unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:linkname testHookHostsPath net.testHookHostsPath
|
|
||||||
var testHookHostsPath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if dir, err := windows.GetSystemDirectory(); err == nil {
|
|
||||||
testHookHostsPath = dir + "/Drivers/etc/hosts"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,9 +19,15 @@ var (
|
|||||||
// DefaultResolver aim to resolve ip
|
// DefaultResolver aim to resolve ip
|
||||||
DefaultResolver Resolver
|
DefaultResolver Resolver
|
||||||
|
|
||||||
// ProxyServerHostResolver resolve ip to proxies server host
|
// ProxyServerHostResolver resolve ip for proxies server host, only nil when DefaultResolver is nil
|
||||||
ProxyServerHostResolver Resolver
|
ProxyServerHostResolver Resolver
|
||||||
|
|
||||||
|
// DirectHostResolver resolve ip for direct outbound host, only nil when DefaultResolver is nil
|
||||||
|
DirectHostResolver Resolver
|
||||||
|
|
||||||
|
// SystemResolver always using system dns, and was init in dns module
|
||||||
|
SystemResolver Resolver
|
||||||
|
|
||||||
// DisableIPv6 means don't resolve ipv6 host
|
// DisableIPv6 means don't resolve ipv6 host
|
||||||
// default value is true
|
// default value is true
|
||||||
DisableIPv6 = true
|
DisableIPv6 = true
|
||||||
@@ -72,14 +77,7 @@ func LookupIPv4WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
return r.LookupIPv4(ctx, host)
|
return r.LookupIPv4(ctx, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip4", host)
|
return SystemResolver.LookupIPv4(ctx, host)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(ipAddrs) == 0 {
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipAddrs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIPv4 with a host, return ipv4 list
|
// LookupIPv4 with a host, return ipv4 list
|
||||||
@@ -128,14 +126,7 @@ func LookupIPv6WithResolver(ctx context.Context, host string, r Resolver) ([]net
|
|||||||
return r.LookupIPv6(ctx, host)
|
return r.LookupIPv6(ctx, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
ipAddrs, err := net.DefaultResolver.LookupNetIP(ctx, "ip6", host)
|
return SystemResolver.LookupIPv6(ctx, host)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(ipAddrs) == 0 {
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ipAddrs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIPv6 with a host, return ipv6 list
|
// LookupIPv6 with a host, return ipv6 list
|
||||||
@@ -177,14 +168,7 @@ func LookupIPWithResolver(ctx context.Context, host string, r Resolver) ([]netip
|
|||||||
return []netip.Addr{ip}, nil
|
return []netip.Addr{ip}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ips, err := net.DefaultResolver.LookupNetIP(ctx, "ip", host)
|
return SystemResolver.LookupIP(ctx, host)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if len(ips) == 0 {
|
|
||||||
return nil, ErrIPNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return ips, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LookupIP with a host, return ip
|
// LookupIP with a host, return ip
|
||||||
@@ -212,58 +196,10 @@ func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
|
|||||||
return ResolveIPWithResolver(ctx, host, DefaultResolver)
|
return ResolveIPWithResolver(ctx, host, DefaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResolveIPv4ProxyServerHost proxies server host only
|
|
||||||
func ResolveIPv4ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return ResolveIPv4WithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return ResolveIPv4(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveIPv6ProxyServerHost proxies server host only
|
|
||||||
func ResolveIPv6ProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return ResolveIPv6WithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return ResolveIPv6(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResolveProxyServerHost proxies server host only
|
|
||||||
func ResolveProxyServerHost(ctx context.Context, host string) (netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return ResolveIPWithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return ResolveIP(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupIPv6ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return LookupIPv6WithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return LookupIPv6(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupIPv4ProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return LookupIPv4WithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return LookupIPv4(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupIPProxyServerHost(ctx context.Context, host string) ([]netip.Addr, error) {
|
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
return LookupIPWithResolver(ctx, host, ProxyServerHostResolver)
|
|
||||||
}
|
|
||||||
return LookupIP(ctx, host)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResetConnection() {
|
func ResetConnection() {
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
go DefaultResolver.ResetConnection()
|
go DefaultResolver.ResetConnection()
|
||||||
}
|
}
|
||||||
if ProxyServerHostResolver != nil {
|
|
||||||
go ProxyServerHostResolver.ResetConnection()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
func SortationAddr(ips []netip.Addr) (ipv4s, ipv6s []netip.Addr) {
|
||||||
|
|||||||
@@ -84,11 +84,14 @@ func NewFileVehicle(path string) *FileVehicle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type HTTPVehicle struct {
|
type HTTPVehicle struct {
|
||||||
url string
|
url string
|
||||||
path string
|
path string
|
||||||
proxy string
|
proxy string
|
||||||
header http.Header
|
header http.Header
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
sizeLimit int64
|
||||||
|
inRead func(response *http.Response)
|
||||||
|
provider types.ProxyProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HTTPVehicle) Url() string {
|
func (h *HTTPVehicle) Url() string {
|
||||||
@@ -111,6 +114,10 @@ func (h *HTTPVehicle) Write(buf []byte) error {
|
|||||||
return safeWrite(h.path, buf)
|
return safeWrite(h.path, buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HTTPVehicle) SetInRead(fn func(response *http.Response)) {
|
||||||
|
h.inRead = fn
|
||||||
|
}
|
||||||
|
|
||||||
func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
|
func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []byte, hash utils.HashType, err error) {
|
||||||
ctx, cancel := context.WithTimeout(ctx, h.timeout)
|
ctx, cancel := context.WithTimeout(ctx, h.timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
@@ -133,6 +140,11 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if h.inRead != nil {
|
||||||
|
h.inRead(resp)
|
||||||
|
}
|
||||||
|
|
||||||
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
if resp.StatusCode < 200 || resp.StatusCode > 299 {
|
||||||
if setIfNoneMatch && resp.StatusCode == http.StatusNotModified {
|
if setIfNoneMatch && resp.StatusCode == http.StatusNotModified {
|
||||||
return nil, oldHash, nil
|
return nil, oldHash, nil
|
||||||
@@ -140,7 +152,11 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
|||||||
err = errors.New(resp.Status)
|
err = errors.New(resp.Status)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
buf, err = io.ReadAll(resp.Body)
|
var reader io.Reader = resp.Body
|
||||||
|
if h.sizeLimit > 0 {
|
||||||
|
reader = io.LimitReader(reader, h.sizeLimit)
|
||||||
|
}
|
||||||
|
buf, err = io.ReadAll(reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -155,12 +171,13 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration) *HTTPVehicle {
|
func NewHTTPVehicle(url string, path string, proxy string, header http.Header, timeout time.Duration, sizeLimit int64) *HTTPVehicle {
|
||||||
return &HTTPVehicle{
|
return &HTTPVehicle{
|
||||||
url: url,
|
url: url,
|
||||||
path: path,
|
path: path,
|
||||||
proxy: proxy,
|
proxy: proxy,
|
||||||
header: header,
|
header: header,
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
sizeLimit: sizeLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,10 @@ func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
|
|||||||
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
|
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
return sd.forceSniff(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sd *Dispatcher) forceSniff(metadata *C.Metadata) bool {
|
||||||
for _, matcher := range sd.forceDomain {
|
for _, matcher := range sd.forceDomain {
|
||||||
if matcher.MatchDomain(metadata.Host) {
|
if matcher.MatchDomain(metadata.Host) {
|
||||||
return true
|
return true
|
||||||
@@ -98,16 +102,21 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
|
|||||||
if !inWhitelist {
|
if !inWhitelist {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
forceSniffer := sd.forceSniff(metadata)
|
||||||
|
|
||||||
dst := metadata.AddrPort()
|
dst := metadata.AddrPort()
|
||||||
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
if !forceSniffer {
|
||||||
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
|
||||||
return false
|
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := sd.sniffDomain(conn, metadata)
|
host, err := sd.sniffDomain(conn, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sd.cacheSniffFailed(metadata)
|
if !forceSniffer {
|
||||||
|
sd.cacheSniffFailed(metadata)
|
||||||
|
}
|
||||||
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
log.Debugln("[Sniffer] All sniffing sniff failed with from [%s:%d] to [%s:%d]", metadata.SrcIP, metadata.SrcPort, metadata.String(), metadata.DstPort)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -145,8 +154,12 @@ func (sd *Dispatcher) Enable() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
|
func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (string, error) {
|
||||||
|
//defer func(start time.Time) {
|
||||||
|
// log.Debugln("[Sniffer] [%s] Sniffing took %s", metadata.DstIP, time.Since(start))
|
||||||
|
//}(time.Now())
|
||||||
|
|
||||||
for s := range sd.sniffers {
|
for s := range sd.sniffers {
|
||||||
if s.SupportNetwork() == C.TCP {
|
if s.SupportNetwork() == C.TCP && s.SupportPort(metadata.DstPort) {
|
||||||
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
_, err := conn.Peek(1)
|
_, err := conn.Peek(1)
|
||||||
_ = conn.SetReadDeadline(time.Time{})
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
@@ -154,7 +167,7 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
|
|||||||
_, ok := err.(*net.OpError)
|
_, ok := err.(*net.OpError)
|
||||||
if ok {
|
if ok {
|
||||||
sd.cacheSniffFailed(metadata)
|
sd.cacheSniffFailed(metadata)
|
||||||
log.Errorln("[Sniffer] [%s] may not have any sent data, Consider adding skip", metadata.DstIP.String())
|
log.Errorln("[Sniffer] [%s] [%s] may not have any sent data, Consider adding skip", metadata.DstIP, s.Protocol())
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,22 +177,36 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
|
|||||||
bufferedLen := conn.Buffered()
|
bufferedLen := conn.Buffered()
|
||||||
bytes, err := conn.Peek(bufferedLen)
|
bytes, err := conn.Peek(bufferedLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugln("[Sniffer] the data length not enough")
|
log.Debugln("[Sniffer] [%s] [%s] the data length not enough, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
host, err := s.SniffData(bytes)
|
host, err := s.SniffData(bytes)
|
||||||
|
var e *errNeedAtLeastData
|
||||||
|
if errors.As(err, &e) {
|
||||||
|
//log.Debugln("[Sniffer] [%s] [%s] %v, got length: %d", metadata.DstIP, s.Protocol(), e, len(bytes))
|
||||||
|
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
|
||||||
|
bytes, err = conn.Peek(e.length)
|
||||||
|
_ = conn.SetReadDeadline(time.Time{})
|
||||||
|
//log.Debugln("[Sniffer] [%s] [%s] try again, got length: %d", metadata.DstIP, s.Protocol(), len(bytes))
|
||||||
|
if err != nil {
|
||||||
|
log.Debugln("[Sniffer] [%s] [%s] the data length not enough, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
host, err = s.SniffData(bytes)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
//log.Debugln("[Sniffer] [%s] [%s] Sniff data failed, error: %v", metadata.DstIP, s.Protocol(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = netip.ParseAddr(host)
|
_, err = netip.ParseAddr(host)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
//log.Debugln("[Sniffer] [%s] Sniff data failed %s", s.Protocol(), metadata.DstIP)
|
//log.Debugln("[Sniffer] [%s] [%s] Sniff data failed, got host [%s]", metadata.DstIP, s.Protocol(), host)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//log.Debugln("[Sniffer] [%s] [%s] Sniffed [%s]", metadata.DstIP, s.Protocol(), host)
|
||||||
return host, nil
|
return host, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package sniffer
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
@@ -15,6 +16,19 @@ var (
|
|||||||
errNotClientHello = errors.New("not client hello")
|
errNotClientHello = errors.New("not client hello")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type errNeedAtLeastData struct {
|
||||||
|
length int
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errNeedAtLeastData) Error() string {
|
||||||
|
return fmt.Sprintf("%v, need at least length: %d", e.err, e.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errNeedAtLeastData) Unwrap() error {
|
||||||
|
return e.err
|
||||||
|
}
|
||||||
|
|
||||||
var _ sniffer.Sniffer = (*TLSSniffer)(nil)
|
var _ sniffer.Sniffer = (*TLSSniffer)(nil)
|
||||||
|
|
||||||
type TLSSniffer struct {
|
type TLSSniffer struct {
|
||||||
@@ -160,7 +174,10 @@ func SniffTLS(b []byte) (*string, error) {
|
|||||||
}
|
}
|
||||||
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
|
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
|
||||||
if 5+headerLen > len(b) {
|
if 5+headerLen > len(b) {
|
||||||
return nil, ErrNoClue
|
return nil, &errNeedAtLeastData{
|
||||||
|
length: 5 + headerLen,
|
||||||
|
err: ErrNoClue,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := ReadClientHello(b[5 : 5+headerLen])
|
domain, err := ReadClientHello(b[5 : 5+headerLen])
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateMMDB() (err error) {
|
func UpdateMMDB() (err error) {
|
||||||
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout)
|
vehicle := resource.NewHTTPVehicle(geodata.MmdbUrl(), C.Path.MMDB(), "", nil, defaultHttpTimeout, 0)
|
||||||
var oldHash utils.HashType
|
var oldHash utils.HashType
|
||||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||||
oldHash = utils.MakeHash(buf)
|
oldHash = utils.MakeHash(buf)
|
||||||
@@ -76,7 +76,7 @@ func UpdateMMDB() (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func UpdateASN() (err error) {
|
func UpdateASN() (err error) {
|
||||||
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout)
|
vehicle := resource.NewHTTPVehicle(geodata.ASNUrl(), C.Path.ASN(), "", nil, defaultHttpTimeout, 0)
|
||||||
var oldHash utils.HashType
|
var oldHash utils.HashType
|
||||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||||
oldHash = utils.MakeHash(buf)
|
oldHash = utils.MakeHash(buf)
|
||||||
@@ -109,7 +109,7 @@ func UpdateASN() (err error) {
|
|||||||
func UpdateGeoIp() (err error) {
|
func UpdateGeoIp() (err error) {
|
||||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||||
|
|
||||||
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout)
|
vehicle := resource.NewHTTPVehicle(geodata.GeoIpUrl(), C.Path.GeoIP(), "", nil, defaultHttpTimeout, 0)
|
||||||
var oldHash utils.HashType
|
var oldHash utils.HashType
|
||||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||||
oldHash = utils.MakeHash(buf)
|
oldHash = utils.MakeHash(buf)
|
||||||
@@ -139,7 +139,7 @@ func UpdateGeoIp() (err error) {
|
|||||||
func UpdateGeoSite() (err error) {
|
func UpdateGeoSite() (err error) {
|
||||||
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
geoLoader, err := geodata.GetGeoDataLoader("standard")
|
||||||
|
|
||||||
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout)
|
vehicle := resource.NewHTTPVehicle(geodata.GeoSiteUrl(), C.Path.GeoSite(), "", nil, defaultHttpTimeout, 0)
|
||||||
var oldHash utils.HashType
|
var oldHash utils.HashType
|
||||||
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
|
||||||
oldHash = utils.MakeHash(buf)
|
oldHash = utils.MakeHash(buf)
|
||||||
@@ -229,20 +229,22 @@ func UpdateGeoDatabases() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUpdateTime() (err error, time time.Time) {
|
func getUpdateTime() (err error, time time.Time) {
|
||||||
var fileInfo os.FileInfo
|
filesToCheck := []string{
|
||||||
if geodata.GeodataMode() {
|
C.Path.GeoIP(),
|
||||||
fileInfo, err = os.Stat(C.Path.GeoIP())
|
C.Path.MMDB(),
|
||||||
if err != nil {
|
C.Path.ASN(),
|
||||||
return err, time
|
C.Path.GeoSite(),
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
fileInfo, err = os.Stat(C.Path.MMDB())
|
for _, file := range filesToCheck {
|
||||||
if err != nil {
|
var fileInfo os.FileInfo
|
||||||
return err, time
|
fileInfo, err = os.Stat(file)
|
||||||
|
if err == nil {
|
||||||
|
return nil, fileInfo.ModTime()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fileInfo.ModTime()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterGeoUpdater() {
|
func RegisterGeoUpdater() {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -14,58 +16,140 @@ import (
|
|||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type UIUpdater struct {
|
||||||
ExternalUIURL string
|
externalUIURL string
|
||||||
ExternalUIPath string
|
externalUIPath string
|
||||||
AutoDownloadUI bool
|
autoDownloadUI bool
|
||||||
|
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type compressionType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
typeUnknown compressionType = iota
|
||||||
|
typeZip
|
||||||
|
typeTarGzip
|
||||||
)
|
)
|
||||||
|
|
||||||
var xdMutex sync.Mutex
|
var DefaultUiUpdater = &UIUpdater{}
|
||||||
|
|
||||||
func DownloadUI() error {
|
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
|
||||||
xdMutex.Lock()
|
updater := &UIUpdater{}
|
||||||
defer xdMutex.Unlock()
|
// checkout externalUI exist
|
||||||
|
if externalUI != "" {
|
||||||
|
updater.autoDownloadUI = true
|
||||||
|
updater.externalUIPath = C.Path.Resolve(externalUI)
|
||||||
|
} else {
|
||||||
|
// default externalUI path
|
||||||
|
updater.externalUIPath = path.Join(C.Path.HomeDir(), "ui")
|
||||||
|
}
|
||||||
|
|
||||||
err := prepareUIPath()
|
// checkout UIpath/name exist
|
||||||
|
if externalUIName != "" {
|
||||||
|
updater.autoDownloadUI = true
|
||||||
|
updater.externalUIPath = path.Join(updater.externalUIPath, externalUIName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if externalUIURL != "" {
|
||||||
|
updater.externalUIURL = externalUIURL
|
||||||
|
}
|
||||||
|
return updater
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UIUpdater) AutoDownloadUI() {
|
||||||
|
u.mutex.Lock()
|
||||||
|
defer u.mutex.Unlock()
|
||||||
|
if u.autoDownloadUI {
|
||||||
|
dirEntries, _ := os.ReadDir(u.externalUIPath)
|
||||||
|
if len(dirEntries) > 0 {
|
||||||
|
log.Infoln("UI already exists, skip downloading")
|
||||||
|
} else {
|
||||||
|
log.Infoln("External UI downloading ...")
|
||||||
|
err := u.downloadUI()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Error downloading UI: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UIUpdater) DownloadUI() error {
|
||||||
|
u.mutex.Lock()
|
||||||
|
defer u.mutex.Unlock()
|
||||||
|
return u.downloadUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectFileType(data []byte) compressionType {
|
||||||
|
if len(data) < 4 {
|
||||||
|
return typeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zip: 0x50 0x4B 0x03 0x04
|
||||||
|
if data[0] == 0x50 && data[1] == 0x4B && data[2] == 0x03 && data[3] == 0x04 {
|
||||||
|
return typeZip
|
||||||
|
}
|
||||||
|
|
||||||
|
// GZip: 0x1F 0x8B
|
||||||
|
if data[0] == 0x1F && data[1] == 0x8B {
|
||||||
|
return typeTarGzip
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UIUpdater) downloadUI() error {
|
||||||
|
err := u.prepareUIPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("prepare UI path failed: %w", err)
|
return fmt.Errorf("prepare UI path failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := downloadForBytes(ExternalUIURL)
|
data, err := downloadForBytes(u.externalUIURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't download file: %w", err)
|
return fmt.Errorf("can't download file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
saved := path.Join(C.Path.HomeDir(), "download.zip")
|
fileType := detectFileType(data)
|
||||||
|
if fileType == typeUnknown {
|
||||||
|
return fmt.Errorf("unknown or unsupported file type")
|
||||||
|
}
|
||||||
|
|
||||||
|
ext := ".zip"
|
||||||
|
if fileType == typeTarGzip {
|
||||||
|
ext = ".tgz"
|
||||||
|
}
|
||||||
|
|
||||||
|
saved := path.Join(C.Path.HomeDir(), "download"+ext)
|
||||||
|
log.Debugln("compression Type: %s", ext)
|
||||||
if err = saveFile(data, saved); err != nil {
|
if err = saveFile(data, saved); err != nil {
|
||||||
return fmt.Errorf("can't save zip file: %w", err)
|
return fmt.Errorf("can't save compressed file: %w", err)
|
||||||
}
|
}
|
||||||
defer os.Remove(saved)
|
defer os.Remove(saved)
|
||||||
|
|
||||||
err = cleanup(ExternalUIPath)
|
err = cleanup(u.externalUIPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("cleanup exist file error: %w", err)
|
return fmt.Errorf("cleanup exist file error: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unzipFolder, err := unzip(saved, C.Path.HomeDir())
|
extractedFolder, err := extract(saved, C.Path.HomeDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("can't extract zip file: %w", err)
|
return fmt.Errorf("can't extract compressed file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Rename(unzipFolder, ExternalUIPath)
|
err = os.Rename(extractedFolder, u.externalUIPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("rename UI folder failed: %w", err)
|
return fmt.Errorf("rename UI folder failed: %w", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareUIPath() error {
|
func (u *UIUpdater) prepareUIPath() error {
|
||||||
if _, err := os.Stat(ExternalUIPath); os.IsNotExist(err) {
|
if _, err := os.Stat(u.externalUIPath); os.IsNotExist(err) {
|
||||||
log.Infoln("dir %s does not exist, creating", ExternalUIPath)
|
log.Infoln("dir %s does not exist, creating", u.externalUIPath)
|
||||||
if err := os.MkdirAll(ExternalUIPath, os.ModePerm); err != nil {
|
if err := os.MkdirAll(u.externalUIPath, os.ModePerm); err != nil {
|
||||||
log.Warnln("create dir %s error: %s", ExternalUIPath, err)
|
log.Warnln("create dir %s error: %s", u.externalUIPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -77,9 +161,66 @@ func unzip(src, dest string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer r.Close()
|
defer r.Close()
|
||||||
var extractedFolder string
|
|
||||||
|
// check whether or not only exists singleRoot dir
|
||||||
|
rootDir := ""
|
||||||
|
isSingleRoot := true
|
||||||
|
rootItemCount := 0
|
||||||
for _, f := range r.File {
|
for _, f := range r.File {
|
||||||
fpath := filepath.Join(dest, f.Name)
|
parts := strings.Split(strings.Trim(f.Name, "/"), "/")
|
||||||
|
if len(parts) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 1 {
|
||||||
|
isDir := strings.HasSuffix(f.Name, "/")
|
||||||
|
if !isDir {
|
||||||
|
isSingleRoot = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootDir == "" {
|
||||||
|
rootDir = parts[0]
|
||||||
|
}
|
||||||
|
rootItemCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootItemCount != 1 {
|
||||||
|
isSingleRoot = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// build the dir of extraction
|
||||||
|
var extractedFolder string
|
||||||
|
if isSingleRoot && rootDir != "" {
|
||||||
|
// if the singleRoot, use it directly
|
||||||
|
log.Debugln("Match the singleRoot")
|
||||||
|
extractedFolder = filepath.Join(dest, rootDir)
|
||||||
|
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||||
|
} else {
|
||||||
|
log.Debugln("Match the multiRoot")
|
||||||
|
// or put the files/dirs into new dir
|
||||||
|
baseName := filepath.Base(src)
|
||||||
|
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||||
|
extractedFolder = filepath.Join(dest, baseName)
|
||||||
|
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
|
||||||
|
}
|
||||||
|
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, f := range r.File {
|
||||||
|
var fpath string
|
||||||
|
if isSingleRoot && rootDir != "" {
|
||||||
|
fpath = filepath.Join(dest, f.Name)
|
||||||
|
} else {
|
||||||
|
fpath = filepath.Join(extractedFolder, f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||||
return "", fmt.Errorf("invalid file path: %s", fpath)
|
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||||
}
|
}
|
||||||
@@ -104,13 +245,158 @@ func unzip(src, dest string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if extractedFolder == "" {
|
}
|
||||||
extractedFolder = filepath.Dir(fpath)
|
return extractedFolder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func untgz(src, dest string) (string, error) {
|
||||||
|
file, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
gzr, err := gzip.NewReader(file)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer gzr.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gzr)
|
||||||
|
|
||||||
|
rootDir := ""
|
||||||
|
isSingleRoot := true
|
||||||
|
rootItemCount := 0
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(cleanTarPath(header.Name), string(os.PathSeparator))
|
||||||
|
if len(parts) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parts) == 1 {
|
||||||
|
isDir := header.Typeflag == tar.TypeDir
|
||||||
|
if !isDir {
|
||||||
|
isSingleRoot = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootDir == "" {
|
||||||
|
rootDir = parts[0]
|
||||||
|
}
|
||||||
|
rootItemCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rootItemCount != 1 {
|
||||||
|
isSingleRoot = false
|
||||||
|
}
|
||||||
|
|
||||||
|
file.Seek(0, 0)
|
||||||
|
gzr, _ = gzip.NewReader(file)
|
||||||
|
tr = tar.NewReader(gzr)
|
||||||
|
|
||||||
|
var extractedFolder string
|
||||||
|
if isSingleRoot && rootDir != "" {
|
||||||
|
log.Debugln("Match the singleRoot")
|
||||||
|
extractedFolder = filepath.Join(dest, rootDir)
|
||||||
|
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||||
|
} else {
|
||||||
|
log.Debugln("Match the multiRoot")
|
||||||
|
baseName := filepath.Base(src)
|
||||||
|
baseName = strings.TrimSuffix(baseName, filepath.Ext(baseName))
|
||||||
|
baseName = strings.TrimSuffix(baseName, ".tar")
|
||||||
|
extractedFolder = filepath.Join(dest, baseName)
|
||||||
|
|
||||||
|
for i := 1; ; i++ {
|
||||||
|
if _, err := os.Stat(extractedFolder); os.IsNotExist(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
extractedFolder = filepath.Join(dest, fmt.Sprintf("%s_%d", baseName, i))
|
||||||
|
}
|
||||||
|
log.Debugln("extractedFolder: %s", extractedFolder)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
var fpath string
|
||||||
|
if isSingleRoot && rootDir != "" {
|
||||||
|
fpath = filepath.Join(dest, cleanTarPath(header.Name))
|
||||||
|
} else {
|
||||||
|
fpath = filepath.Join(extractedFolder, cleanTarPath(header.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
|
||||||
|
return "", fmt.Errorf("invalid file path: %s", fpath)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
if err = os.MkdirAll(fpath, os.FileMode(header.Mode)); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(header.Mode))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(outFile, tr); err != nil {
|
||||||
|
outFile.Close()
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
outFile.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return extractedFolder, nil
|
return extractedFolder, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extract(src, dest string) (string, error) {
|
||||||
|
srcLower := strings.ToLower(src)
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(srcLower, ".tar.gz") ||
|
||||||
|
strings.HasSuffix(srcLower, ".tgz"):
|
||||||
|
return untgz(src, dest)
|
||||||
|
case strings.HasSuffix(srcLower, ".zip"):
|
||||||
|
return unzip(src, dest)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported file format: %s", src)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanTarPath(path string) string {
|
||||||
|
// remove prefix ./ or ../
|
||||||
|
path = strings.TrimPrefix(path, "./")
|
||||||
|
path = strings.TrimPrefix(path, "../")
|
||||||
|
|
||||||
|
// normalize path
|
||||||
|
path = filepath.Clean(path)
|
||||||
|
|
||||||
|
// transfer delimiters to system std
|
||||||
|
path = filepath.FromSlash(path)
|
||||||
|
|
||||||
|
// remove prefix path delimiters
|
||||||
|
path = strings.TrimPrefix(path, string(os.PathSeparator))
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func cleanup(root string) error {
|
func cleanup(root string) error {
|
||||||
if _, err := os.Stat(root); os.IsNotExist(err) {
|
if _, err := os.Stat(root); os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
116
config/config.go
116
config/config.go
@@ -7,9 +7,9 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter"
|
"github.com/metacubex/mihomo/adapter"
|
||||||
"github.com/metacubex/mihomo/adapter/outbound"
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
@@ -20,15 +20,10 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/cidr"
|
"github.com/metacubex/mihomo/component/cidr"
|
||||||
"github.com/metacubex/mihomo/component/fakeip"
|
"github.com/metacubex/mihomo/component/fakeip"
|
||||||
"github.com/metacubex/mihomo/component/geodata"
|
"github.com/metacubex/mihomo/component/geodata"
|
||||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
|
||||||
"github.com/metacubex/mihomo/component/keepalive"
|
|
||||||
P "github.com/metacubex/mihomo/component/process"
|
P "github.com/metacubex/mihomo/component/process"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
"github.com/metacubex/mihomo/component/resource"
|
|
||||||
"github.com/metacubex/mihomo/component/sniffer"
|
"github.com/metacubex/mihomo/component/sniffer"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
|
||||||
"github.com/metacubex/mihomo/component/trie"
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
"github.com/metacubex/mihomo/component/updater"
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
providerTypes "github.com/metacubex/mihomo/constant/provider"
|
providerTypes "github.com/metacubex/mihomo/constant/provider"
|
||||||
snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
|
snifferTypes "github.com/metacubex/mihomo/constant/sniffer"
|
||||||
@@ -67,6 +62,9 @@ type General struct {
|
|||||||
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
GlobalClientFingerprint string `json:"global-client-fingerprint"`
|
||||||
GlobalUA string `json:"global-ua"`
|
GlobalUA string `json:"global-ua"`
|
||||||
ETagSupport bool `json:"etag-support"`
|
ETagSupport bool `json:"etag-support"`
|
||||||
|
KeepAliveIdle int `json:"keep-alive-idle"`
|
||||||
|
KeepAliveInterval int `json:"keep-alive-interval"`
|
||||||
|
DisableKeepAlive bool `json:"disable-keep-alive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound config
|
// Inbound config
|
||||||
@@ -105,6 +103,8 @@ type Controller struct {
|
|||||||
ExternalControllerUnix string
|
ExternalControllerUnix string
|
||||||
ExternalControllerPipe string
|
ExternalControllerPipe string
|
||||||
ExternalUI string
|
ExternalUI string
|
||||||
|
ExternalUIURL string
|
||||||
|
ExternalUIName string
|
||||||
ExternalDohServer string
|
ExternalDohServer string
|
||||||
Secret string
|
Secret string
|
||||||
Cors Cors
|
Cors Cors
|
||||||
@@ -160,6 +160,8 @@ type DNS struct {
|
|||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
NameServerPolicy []dns.Policy
|
NameServerPolicy []dns.Policy
|
||||||
ProxyServerNameserver []dns.NameServer
|
ProxyServerNameserver []dns.NameServer
|
||||||
|
DirectNameServer []dns.NameServer
|
||||||
|
DirectFollowPolicy bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile config
|
// Profile config
|
||||||
@@ -203,25 +205,27 @@ type RawCors struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type RawDNS struct {
|
type RawDNS struct {
|
||||||
Enable bool `yaml:"enable" json:"enable"`
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
PreferH3 bool `yaml:"prefer-h3" json:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
IPv6 bool `yaml:"ipv6" json:"ipv6"`
|
||||||
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
IPv6Timeout uint `yaml:"ipv6-timeout" json:"ipv6-timeout"`
|
||||||
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
UseHosts bool `yaml:"use-hosts" json:"use-hosts"`
|
||||||
UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"`
|
UseSystemHosts bool `yaml:"use-system-hosts" json:"use-system-hosts"`
|
||||||
RespectRules bool `yaml:"respect-rules" json:"respect-rules"`
|
RespectRules bool `yaml:"respect-rules" json:"respect-rules"`
|
||||||
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
NameServer []string `yaml:"nameserver" json:"nameserver"`
|
||||||
Fallback []string `yaml:"fallback" json:"fallback"`
|
Fallback []string `yaml:"fallback" json:"fallback"`
|
||||||
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
FallbackFilter RawFallbackFilter `yaml:"fallback-filter" json:"fallback-filter"`
|
||||||
Listen string `yaml:"listen" json:"listen"`
|
Listen string `yaml:"listen" json:"listen"`
|
||||||
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
|
EnhancedMode C.DNSMode `yaml:"enhanced-mode" json:"enhanced-mode"`
|
||||||
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
|
FakeIPRange string `yaml:"fake-ip-range" json:"fake-ip-range"`
|
||||||
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
|
FakeIPFilter []string `yaml:"fake-ip-filter" json:"fake-ip-filter"`
|
||||||
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
|
FakeIPFilterMode C.FilterMode `yaml:"fake-ip-filter-mode" json:"fake-ip-filter-mode"`
|
||||||
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
DefaultNameserver []string `yaml:"default-nameserver" json:"default-nameserver"`
|
||||||
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
|
CacheAlgorithm string `yaml:"cache-algorithm" json:"cache-algorithm"`
|
||||||
NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"`
|
NameServerPolicy *orderedmap.OrderedMap[string, any] `yaml:"nameserver-policy" json:"nameserver-policy"`
|
||||||
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
ProxyServerNameserver []string `yaml:"proxy-server-nameserver" json:"proxy-server-nameserver"`
|
||||||
|
DirectNameServer []string `yaml:"direct-nameserver" json:"direct-nameserver"`
|
||||||
|
DirectNameServerFollowPolicy bool `yaml:"direct-nameserver-follow-policy" json:"direct-nameserver-follow-policy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RawFallbackFilter struct {
|
type RawFallbackFilter struct {
|
||||||
@@ -582,10 +586,11 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
}
|
}
|
||||||
config.General = general
|
config.General = general
|
||||||
|
|
||||||
if len(config.General.GlobalClientFingerprint) != 0 {
|
// We need to temporarily apply some configuration in general and roll back after parsing the complete configuration.
|
||||||
log.Debugln("GlobalClientFingerprint: %s", config.General.GlobalClientFingerprint)
|
// The loading and downloading of geodata in the parseRules and parseRuleProviders rely on these.
|
||||||
tlsC.SetGlobalUtlsClient(config.General.GlobalClientFingerprint)
|
// This implementation is very disgusting, but there is currently no better solution
|
||||||
}
|
rollback := temporaryUpdateGeneral(config.General)
|
||||||
|
defer rollback()
|
||||||
|
|
||||||
controller, err := parseController(rawCfg)
|
controller, err := parseController(rawCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -701,46 +706,10 @@ func ParseRawConfig(rawCfg *RawConfig) (*Config, error) {
|
|||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname temporaryUpdateGeneral
|
||||||
|
func temporaryUpdateGeneral(general *General) func()
|
||||||
|
|
||||||
func parseGeneral(cfg *RawConfig) (*General, error) {
|
func parseGeneral(cfg *RawConfig) (*General, error) {
|
||||||
updater.SetGeoAutoUpdate(cfg.GeoAutoUpdate)
|
|
||||||
updater.SetGeoUpdateInterval(cfg.GeoUpdateInterval)
|
|
||||||
geodata.SetGeodataMode(cfg.GeodataMode)
|
|
||||||
geodata.SetLoader(cfg.GeodataLoader)
|
|
||||||
geodata.SetSiteMatcher(cfg.GeositeMatcher)
|
|
||||||
geodata.SetGeoIpUrl(cfg.GeoXUrl.GeoIp)
|
|
||||||
geodata.SetGeoSiteUrl(cfg.GeoXUrl.GeoSite)
|
|
||||||
geodata.SetMmdbUrl(cfg.GeoXUrl.Mmdb)
|
|
||||||
geodata.SetASNUrl(cfg.GeoXUrl.ASN)
|
|
||||||
mihomoHttp.SetUA(cfg.GlobalUA)
|
|
||||||
resource.SetETag(cfg.ETagSupport)
|
|
||||||
|
|
||||||
if cfg.KeepAliveIdle != 0 {
|
|
||||||
keepalive.SetKeepAliveIdle(time.Duration(cfg.KeepAliveIdle) * time.Second)
|
|
||||||
}
|
|
||||||
if cfg.KeepAliveInterval != 0 {
|
|
||||||
keepalive.SetKeepAliveInterval(time.Duration(cfg.KeepAliveInterval) * time.Second)
|
|
||||||
}
|
|
||||||
keepalive.SetDisableKeepAlive(cfg.DisableKeepAlive)
|
|
||||||
|
|
||||||
// checkout externalUI exist
|
|
||||||
if cfg.ExternalUI != "" {
|
|
||||||
updater.AutoDownloadUI = true
|
|
||||||
updater.ExternalUIPath = C.Path.Resolve(cfg.ExternalUI)
|
|
||||||
} else {
|
|
||||||
// default externalUI path
|
|
||||||
updater.ExternalUIPath = path.Join(C.Path.HomeDir(), "ui")
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkout UIpath/name exist
|
|
||||||
if cfg.ExternalUIName != "" {
|
|
||||||
updater.AutoDownloadUI = true
|
|
||||||
updater.ExternalUIPath = path.Join(updater.ExternalUIPath, cfg.ExternalUIName)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.ExternalUIURL != "" {
|
|
||||||
updater.ExternalUIURL = cfg.ExternalUIURL
|
|
||||||
}
|
|
||||||
|
|
||||||
return &General{
|
return &General{
|
||||||
Inbound: Inbound{
|
Inbound: Inbound{
|
||||||
Port: cfg.Port,
|
Port: cfg.Port,
|
||||||
@@ -774,11 +743,15 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
|
|||||||
GeoUpdateInterval: cfg.GeoUpdateInterval,
|
GeoUpdateInterval: cfg.GeoUpdateInterval,
|
||||||
GeodataMode: cfg.GeodataMode,
|
GeodataMode: cfg.GeodataMode,
|
||||||
GeodataLoader: cfg.GeodataLoader,
|
GeodataLoader: cfg.GeodataLoader,
|
||||||
|
GeositeMatcher: cfg.GeositeMatcher,
|
||||||
TCPConcurrent: cfg.TCPConcurrent,
|
TCPConcurrent: cfg.TCPConcurrent,
|
||||||
FindProcessMode: cfg.FindProcessMode,
|
FindProcessMode: cfg.FindProcessMode,
|
||||||
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
GlobalClientFingerprint: cfg.GlobalClientFingerprint,
|
||||||
GlobalUA: cfg.GlobalUA,
|
GlobalUA: cfg.GlobalUA,
|
||||||
ETagSupport: cfg.ETagSupport,
|
ETagSupport: cfg.ETagSupport,
|
||||||
|
KeepAliveIdle: cfg.KeepAliveIdle,
|
||||||
|
KeepAliveInterval: cfg.KeepAliveInterval,
|
||||||
|
DisableKeepAlive: cfg.DisableKeepAlive,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -786,6 +759,8 @@ func parseController(cfg *RawConfig) (*Controller, error) {
|
|||||||
return &Controller{
|
return &Controller{
|
||||||
ExternalController: cfg.ExternalController,
|
ExternalController: cfg.ExternalController,
|
||||||
ExternalUI: cfg.ExternalUI,
|
ExternalUI: cfg.ExternalUI,
|
||||||
|
ExternalUIURL: cfg.ExternalUIURL,
|
||||||
|
ExternalUIName: cfg.ExternalUIName,
|
||||||
Secret: cfg.Secret,
|
Secret: cfg.Secret,
|
||||||
ExternalControllerPipe: cfg.ExternalControllerPipe,
|
ExternalControllerPipe: cfg.ExternalControllerPipe,
|
||||||
ExternalControllerUnix: cfg.ExternalControllerUnix,
|
ExternalControllerUnix: cfg.ExternalControllerUnix,
|
||||||
@@ -1423,6 +1398,11 @@ func parseDNS(rawCfg *RawConfig, hosts *trie.DomainTrie[resolver.HostValue], rul
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dnsCfg.DirectNameServer, err = parseNameServer(cfg.DirectNameServer, false, cfg.PreferH3); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dnsCfg.DirectFollowPolicy = cfg.DirectNameServerFollowPolicy
|
||||||
|
|
||||||
if len(cfg.DefaultNameserver) == 0 {
|
if len(cfg.DefaultNameserver) == 0 {
|
||||||
return nil, errors.New("default nameserver should have at least one nameserver")
|
return nil, errors.New("default nameserver should have at least one nameserver")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ const (
|
|||||||
WireGuard
|
WireGuard
|
||||||
Tuic
|
Tuic
|
||||||
Ssh
|
Ssh
|
||||||
|
Mieru
|
||||||
|
AnyTLS
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -99,13 +101,24 @@ type Dialer interface {
|
|||||||
ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error)
|
ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ProxyInfo struct {
|
||||||
|
XUDP bool
|
||||||
|
TFO bool
|
||||||
|
MPTCP bool
|
||||||
|
SMUX bool
|
||||||
|
Interface string
|
||||||
|
RoutingMark int
|
||||||
|
DialerProxy string
|
||||||
|
}
|
||||||
|
|
||||||
type ProxyAdapter interface {
|
type ProxyAdapter interface {
|
||||||
Name() string
|
Name() string
|
||||||
Type() AdapterType
|
Type() AdapterType
|
||||||
Addr() string
|
Addr() string
|
||||||
SupportUDP() bool
|
SupportUDP() bool
|
||||||
SupportXUDP() bool
|
|
||||||
SupportTFO() bool
|
// ProxyInfo contains some extra information maybe useful for MarshalJSON
|
||||||
|
ProxyInfo() ProxyInfo
|
||||||
MarshalJSON() ([]byte, error)
|
MarshalJSON() ([]byte, error)
|
||||||
|
|
||||||
// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
|
// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
|
||||||
@@ -213,7 +226,12 @@ func (at AdapterType) String() string {
|
|||||||
return "WireGuard"
|
return "WireGuard"
|
||||||
case Tuic:
|
case Tuic:
|
||||||
return "Tuic"
|
return "Tuic"
|
||||||
|
case Ssh:
|
||||||
|
return "Ssh"
|
||||||
|
case Mieru:
|
||||||
|
return "Mieru"
|
||||||
|
case AnyTLS:
|
||||||
|
return "AnyTLS"
|
||||||
case Relay:
|
case Relay:
|
||||||
return "Relay"
|
return "Relay"
|
||||||
case Selector:
|
case Selector:
|
||||||
@@ -224,8 +242,6 @@ func (at AdapterType) String() string {
|
|||||||
return "URLTest"
|
return "URLTest"
|
||||||
case LoadBalance:
|
case LoadBalance:
|
||||||
return "LoadBalance"
|
return "LoadBalance"
|
||||||
case Ssh:
|
|
||||||
return "Ssh"
|
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ const (
|
|||||||
SOCKS5
|
SOCKS5
|
||||||
SHADOWSOCKS
|
SHADOWSOCKS
|
||||||
VMESS
|
VMESS
|
||||||
|
VLESS
|
||||||
REDIR
|
REDIR
|
||||||
TPROXY
|
TPROXY
|
||||||
|
TROJAN
|
||||||
TUNNEL
|
TUNNEL
|
||||||
TUN
|
TUN
|
||||||
TUIC
|
TUIC
|
||||||
HYSTERIA2
|
HYSTERIA2
|
||||||
|
ANYTLS
|
||||||
INNER
|
INNER
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -69,10 +72,14 @@ func (t Type) String() string {
|
|||||||
return "ShadowSocks"
|
return "ShadowSocks"
|
||||||
case VMESS:
|
case VMESS:
|
||||||
return "Vmess"
|
return "Vmess"
|
||||||
|
case VLESS:
|
||||||
|
return "Vless"
|
||||||
case REDIR:
|
case REDIR:
|
||||||
return "Redir"
|
return "Redir"
|
||||||
case TPROXY:
|
case TPROXY:
|
||||||
return "TProxy"
|
return "TProxy"
|
||||||
|
case TROJAN:
|
||||||
|
return "Trojan"
|
||||||
case TUNNEL:
|
case TUNNEL:
|
||||||
return "Tunnel"
|
return "Tunnel"
|
||||||
case TUN:
|
case TUN:
|
||||||
@@ -81,6 +88,8 @@ func (t Type) String() string {
|
|||||||
return "Tuic"
|
return "Tuic"
|
||||||
case HYSTERIA2:
|
case HYSTERIA2:
|
||||||
return "Hysteria2"
|
return "Hysteria2"
|
||||||
|
case ANYTLS:
|
||||||
|
return "AnyTLS"
|
||||||
case INNER:
|
case INNER:
|
||||||
return "Inner"
|
return "Inner"
|
||||||
default:
|
default:
|
||||||
@@ -103,10 +112,14 @@ func ParseType(t string) (*Type, error) {
|
|||||||
res = SHADOWSOCKS
|
res = SHADOWSOCKS
|
||||||
case "VMESS":
|
case "VMESS":
|
||||||
res = VMESS
|
res = VMESS
|
||||||
|
case "VLESS":
|
||||||
|
res = VLESS
|
||||||
case "REDIR":
|
case "REDIR":
|
||||||
res = REDIR
|
res = REDIR
|
||||||
case "TPROXY":
|
case "TPROXY":
|
||||||
res = TPROXY
|
res = TPROXY
|
||||||
|
case "TROJAN":
|
||||||
|
res = TROJAN
|
||||||
case "TUNNEL":
|
case "TUNNEL":
|
||||||
res = TUNNEL
|
res = TUNNEL
|
||||||
case "TUN":
|
case "TUN":
|
||||||
@@ -115,6 +128,8 @@ func ParseType(t string) (*Type, error) {
|
|||||||
res = TUIC
|
res = TUIC
|
||||||
case "HYSTERIA2":
|
case "HYSTERIA2":
|
||||||
res = HYSTERIA2
|
res = HYSTERIA2
|
||||||
|
case "ANYTLS":
|
||||||
|
res = ANYTLS
|
||||||
case "INNER":
|
case "INNER":
|
||||||
res = INNER
|
res = INNER
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const (
|
|||||||
File VehicleType = iota
|
File VehicleType = iota
|
||||||
HTTP
|
HTTP
|
||||||
Compatible
|
Compatible
|
||||||
|
Inline
|
||||||
)
|
)
|
||||||
|
|
||||||
// VehicleType defined
|
// VehicleType defined
|
||||||
@@ -26,6 +27,8 @@ func (v VehicleType) String() string {
|
|||||||
return "HTTP"
|
return "HTTP"
|
||||||
case Compatible:
|
case Compatible:
|
||||||
return "Compatible"
|
return "Compatible"
|
||||||
|
case Inline:
|
||||||
|
return "Inline"
|
||||||
default:
|
default:
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,13 +37,14 @@ const (
|
|||||||
transportDefaultIdleConnTimeout = 5 * time.Minute
|
transportDefaultIdleConnTimeout = 5 * time.Minute
|
||||||
|
|
||||||
// dohMaxConnsPerHost controls the maximum number of connections for
|
// dohMaxConnsPerHost controls the maximum number of connections for
|
||||||
// each host.
|
// each host. Note, that setting it to 1 may cause issues with Go's http
|
||||||
dohMaxConnsPerHost = 1
|
// implementation, see https://github.com/AdguardTeam/dnsproxy/issues/278.
|
||||||
|
dohMaxConnsPerHost = 2
|
||||||
dialTimeout = 10 * time.Second
|
dialTimeout = 10 * time.Second
|
||||||
|
|
||||||
// dohMaxIdleConns controls the maximum number of connections being idle
|
// dohMaxIdleConns controls the maximum number of connections being idle
|
||||||
// at the same time.
|
// at the same time.
|
||||||
dohMaxIdleConns = 1
|
dohMaxIdleConns = 2
|
||||||
maxElapsedTime = time.Second * 30
|
maxElapsedTime = time.Second * 30
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ func FlushCacheWithDefaultResolver() {
|
|||||||
if r := resolver.DefaultResolver; r != nil {
|
if r := resolver.DefaultResolver; r != nil {
|
||||||
r.ClearCache()
|
r.ClearCache()
|
||||||
}
|
}
|
||||||
|
if r := resolver.SystemResolver; r != nil {
|
||||||
|
r.ClearCache()
|
||||||
|
}
|
||||||
resolver.ResetConnection()
|
resolver.ResetConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
"github.com/metacubex/mihomo/component/trie"
|
"github.com/metacubex/mihomo/component/trie"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/constant/provider"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
@@ -428,6 +427,8 @@ type Config struct {
|
|||||||
Main, Fallback []NameServer
|
Main, Fallback []NameServer
|
||||||
Default []NameServer
|
Default []NameServer
|
||||||
ProxyServer []NameServer
|
ProxyServer []NameServer
|
||||||
|
DirectServer []NameServer
|
||||||
|
DirectFollowPolicy bool
|
||||||
IPv6 bool
|
IPv6 bool
|
||||||
IPv6Timeout uint
|
IPv6Timeout uint
|
||||||
EnhancedMode C.DNSMode
|
EnhancedMode C.DNSMode
|
||||||
@@ -436,7 +437,6 @@ type Config struct {
|
|||||||
Pool *fakeip.Pool
|
Pool *fakeip.Pool
|
||||||
Hosts *trie.DomainTrie[resolver.HostValue]
|
Hosts *trie.DomainTrie[resolver.HostValue]
|
||||||
Policy []Policy
|
Policy []Policy
|
||||||
Tunnel provider.Tunnel
|
|
||||||
CacheAlgorithm string
|
CacheAlgorithm string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +448,25 @@ func (config Config) newCache() dnsCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResolver(config Config) (r *Resolver, pr *Resolver) {
|
type Resolvers struct {
|
||||||
|
*Resolver
|
||||||
|
ProxyResolver *Resolver
|
||||||
|
DirectResolver *Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs Resolvers) ClearCache() {
|
||||||
|
rs.Resolver.ClearCache()
|
||||||
|
rs.ProxyResolver.ClearCache()
|
||||||
|
rs.DirectResolver.ClearCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs Resolvers) ResetConnection() {
|
||||||
|
rs.Resolver.ResetConnection()
|
||||||
|
rs.ProxyResolver.ResetConnection()
|
||||||
|
rs.DirectResolver.ResetConnection()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewResolver(config Config) (rs Resolvers) {
|
||||||
defaultResolver := &Resolver{
|
defaultResolver := &Resolver{
|
||||||
main: transform(config.Default, nil),
|
main: transform(config.Default, nil),
|
||||||
cache: config.newCache(),
|
cache: config.newCache(),
|
||||||
@@ -482,7 +500,7 @@ func NewResolver(config Config) (r *Resolver, pr *Resolver) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
r = &Resolver{
|
r := &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: cacheTransform(config.Main),
|
main: cacheTransform(config.Main),
|
||||||
cache: config.newCache(),
|
cache: config.newCache(),
|
||||||
@@ -490,9 +508,10 @@ func NewResolver(config Config) (r *Resolver, pr *Resolver) {
|
|||||||
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||||
}
|
}
|
||||||
r.defaultResolver = defaultResolver
|
r.defaultResolver = defaultResolver
|
||||||
|
rs.Resolver = r
|
||||||
|
|
||||||
if len(config.ProxyServer) != 0 {
|
if len(config.ProxyServer) != 0 {
|
||||||
pr = &Resolver{
|
rs.ProxyResolver = &Resolver{
|
||||||
ipv6: config.IPv6,
|
ipv6: config.IPv6,
|
||||||
main: cacheTransform(config.ProxyServer),
|
main: cacheTransform(config.ProxyServer),
|
||||||
cache: config.newCache(),
|
cache: config.newCache(),
|
||||||
@@ -501,8 +520,20 @@ func NewResolver(config Config) (r *Resolver, pr *Resolver) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(config.DirectServer) != 0 {
|
||||||
|
rs.DirectResolver = &Resolver{
|
||||||
|
ipv6: config.IPv6,
|
||||||
|
main: cacheTransform(config.DirectServer),
|
||||||
|
cache: config.newCache(),
|
||||||
|
hosts: config.Hosts,
|
||||||
|
ipv6Timeout: time.Duration(config.IPv6Timeout) * time.Millisecond,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(config.Fallback) != 0 {
|
if len(config.Fallback) != 0 {
|
||||||
r.fallback = cacheTransform(config.Fallback)
|
r.fallback = cacheTransform(config.Fallback)
|
||||||
|
r.fallbackIPFilters = config.FallbackIPFilter
|
||||||
|
r.fallbackDomainFilters = config.FallbackDomainFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.Policy) != 0 {
|
if len(config.Policy) != 0 {
|
||||||
@@ -531,9 +562,11 @@ func NewResolver(config Config) (r *Resolver, pr *Resolver) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
insertPolicy(nil)
|
insertPolicy(nil)
|
||||||
|
|
||||||
|
if rs.DirectResolver != nil && config.DirectFollowPolicy {
|
||||||
|
rs.DirectResolver.policy = r.policy
|
||||||
|
}
|
||||||
}
|
}
|
||||||
r.fallbackIPFilters = config.FallbackIPFilter
|
|
||||||
r.fallbackDomainFilters = config.FallbackDomainFilter
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"github.com/metacubex/mihomo/common/sockopt"
|
"github.com/metacubex/mihomo/common/sockopt"
|
||||||
"github.com/metacubex/mihomo/context"
|
"github.com/metacubex/mihomo/context"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
@@ -20,8 +21,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*D.Server
|
handler handler
|
||||||
handler handler
|
tcpServer *D.Server
|
||||||
|
udpServer *D.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeDNS implement D.Handler ServeDNS
|
// ServeDNS implement D.Handler ServeDNS
|
||||||
@@ -55,12 +57,19 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if server.Server != nil {
|
if server.tcpServer != nil {
|
||||||
server.Shutdown()
|
_ = server.tcpServer.Shutdown()
|
||||||
server = &Server{}
|
server.tcpServer = nil
|
||||||
address = ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if server.udpServer != nil {
|
||||||
|
_ = server.udpServer.Shutdown()
|
||||||
|
server.udpServer = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
server.handler = nil
|
||||||
|
address = ""
|
||||||
|
|
||||||
if addr == "" {
|
if addr == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -77,31 +86,36 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
udpAddr, err := net.ResolveUDPAddr("udp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := net.ListenUDP("udp", udpAddr)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sockopt.UDPReuseaddr(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
address = addr
|
address = addr
|
||||||
handler := NewHandler(resolver, mapper)
|
handler := NewHandler(resolver, mapper)
|
||||||
server = &Server{handler: handler}
|
server = &Server{handler: handler}
|
||||||
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
server.ActivateAndServe()
|
p, err := inbound.ListenPacket("udp", addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Start DNS server(UDP) error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sockopt.UDPReuseaddr(p); err != nil {
|
||||||
|
log.Warnln("Failed to Reuse UDP Address: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("DNS server(UDP) listening at: %s", p.LocalAddr().String())
|
||||||
|
server.udpServer = &D.Server{Addr: addr, PacketConn: p, Handler: server}
|
||||||
|
_ = server.udpServer.ActivateAndServe()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
l, err := inbound.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorln("Start DNS server(TCP) error: %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infoln("DNS server(TCP) listening at: %s", l.Addr().String())
|
||||||
|
server.tcpServer = &D.Server{Addr: addr, Listener: l, Handler: server}
|
||||||
|
_ = server.tcpServer.ActivateAndServe()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Infoln("DNS server listening at: %s", p.LocalAddr().String())
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
D "github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,10 +26,15 @@ type systemClient struct {
|
|||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
dnsClients map[string]*systemDnsClient
|
dnsClients map[string]*systemDnsClient
|
||||||
lastFlush time.Time
|
lastFlush time.Time
|
||||||
|
defaultNS []dnsClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
|
||||||
dnsClients, err := c.getDnsClients()
|
dnsClients, err := c.getDnsClients()
|
||||||
|
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
|
||||||
|
dnsClients = c.defaultNS
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -38,11 +45,16 @@ func (c *systemClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Ms
|
|||||||
// Address implements dnsClient
|
// Address implements dnsClient
|
||||||
func (c *systemClient) Address() string {
|
func (c *systemClient) Address() string {
|
||||||
dnsClients, _ := c.getDnsClients()
|
dnsClients, _ := c.getDnsClients()
|
||||||
|
isDefault := ""
|
||||||
|
if len(dnsClients) == 0 && len(c.defaultNS) > 0 {
|
||||||
|
dnsClients = c.defaultNS
|
||||||
|
isDefault = "[defaultNS]"
|
||||||
|
}
|
||||||
addrs := make([]string, 0, len(dnsClients))
|
addrs := make([]string, 0, len(dnsClients))
|
||||||
for _, c := range dnsClients {
|
for _, c := range dnsClients {
|
||||||
addrs = append(addrs, c.Address())
|
addrs = append(addrs, c.Address())
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("system(%s)", strings.Join(addrs, ","))
|
return fmt.Sprintf("system%s(%s)", isDefault, strings.Join(addrs, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ dnsClient = (*systemClient)(nil)
|
var _ dnsClient = (*systemClient)(nil)
|
||||||
@@ -52,3 +64,11 @@ func newSystemClient() *systemClient {
|
|||||||
dnsClients: map[string]*systemDnsClient{},
|
dnsClients: map[string]*systemDnsClient{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
r := NewResolver(Config{})
|
||||||
|
c := newSystemClient()
|
||||||
|
c.defaultNS = transform([]NameServer{{Addr: "114.114.114.114:53"}, {Addr: "8.8.8.8:53"}}, nil)
|
||||||
|
r.main = []dnsClient{c}
|
||||||
|
resolver.SystemResolver = r
|
||||||
|
}
|
||||||
|
|||||||
172
docs/config.yaml
172
docs/config.yaml
@@ -61,7 +61,7 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
|
|||||||
# RESTful API CORS标头配置
|
# RESTful API CORS标头配置
|
||||||
external-controller-cors:
|
external-controller-cors:
|
||||||
allow-origins:
|
allow-origins:
|
||||||
- *
|
- "*"
|
||||||
allow-private-network: true
|
allow-private-network: true
|
||||||
|
|
||||||
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 )
|
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用,即大于等于1803/RS4版本即可使用 )
|
||||||
@@ -78,6 +78,7 @@ external-controller-pipe: \\.\pipe\mihomo
|
|||||||
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
|
||||||
external-ui: /path/to/ui/folder/
|
external-ui: /path/to/ui/folder/
|
||||||
external-ui-name: xd
|
external-ui-name: xd
|
||||||
|
# 目前支持下载zip,tgz格式的压缩包
|
||||||
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
|
||||||
|
|
||||||
# 在RESTful API端口上开启DOH服务器
|
# 在RESTful API端口上开启DOH服务器
|
||||||
@@ -294,10 +295,15 @@ dns:
|
|||||||
# - tcp://1.1.1.1
|
# - tcp://1.1.1.1
|
||||||
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
# - 'tcp://1.1.1.1#ProxyGroupName' # 指定 DNS 过代理查询,ProxyGroupName 为策略组名或节点名,过代理配置优先于配置出口网卡,当找不到策略组或节点名则设置为出口网卡
|
||||||
|
|
||||||
# 专用于节点域名解析的 DNS 服务器,非必要配置项
|
# 专用于节点域名解析的 DNS 服务器,非必要配置项,如果不填则遵循nameserver-policy、nameserver和fallback的配置
|
||||||
# proxy-server-nameserver:
|
# proxy-server-nameserver:
|
||||||
# - https://dns.google/dns-query
|
# - https://dns.google/dns-query
|
||||||
# - tls://one.one.one.one
|
# - tls://one.one.one.one
|
||||||
|
|
||||||
|
# 专用于direct出口域名解析的 DNS 服务器,非必要配置项,如果不填则遵循nameserver-policy、nameserver和fallback的配置
|
||||||
|
# direct-nameserver:
|
||||||
|
# - system://
|
||||||
|
# direct-nameserver-follow-policy: false # 是否遵循nameserver-policy,默认为不遵守,仅当direct-nameserver不为空时生效
|
||||||
|
|
||||||
# 配置 fallback 使用条件
|
# 配置 fallback 使用条件
|
||||||
# fallback-filter:
|
# fallback-filter:
|
||||||
@@ -742,6 +748,11 @@ proxies: # socks5
|
|||||||
# - h3
|
# - h3
|
||||||
# ca: "./my.ca"
|
# ca: "./my.ca"
|
||||||
# ca-str: "xyz"
|
# ca-str: "xyz"
|
||||||
|
###quic-go特殊配置项,不要随意修改除非你知道你在干什么###
|
||||||
|
# initial-stream-receive-window: 8388608
|
||||||
|
# max-stream-receive-window: 8388608
|
||||||
|
# initial-connection-receive-window: 20971520
|
||||||
|
# max-connection-receive-window: 20971520
|
||||||
|
|
||||||
# wireguard
|
# wireguard
|
||||||
- name: "wg"
|
- name: "wg"
|
||||||
@@ -841,6 +852,35 @@ proxies: # socks5
|
|||||||
password: password
|
password: password
|
||||||
privateKey: path
|
privateKey: path
|
||||||
|
|
||||||
|
# mieru
|
||||||
|
- name: mieru
|
||||||
|
type: mieru
|
||||||
|
server: 1.2.3.4
|
||||||
|
port: 2999
|
||||||
|
# port-range: 2090-2099 #(不可同时填写 port 和 port-range)
|
||||||
|
transport: TCP # 只支持 TCP
|
||||||
|
username: user
|
||||||
|
password: password
|
||||||
|
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
|
||||||
|
# multiplexing: MULTIPLEXING_LOW
|
||||||
|
|
||||||
|
# anytls
|
||||||
|
- name: anytls
|
||||||
|
type: anytls
|
||||||
|
server: 1.2.3.4
|
||||||
|
port: 443
|
||||||
|
password: "<your password>"
|
||||||
|
# client-fingerprint: chrome
|
||||||
|
udp: true
|
||||||
|
# idle-session-check-interval: 30 # seconds
|
||||||
|
# idle-session-timeout: 30 # seconds
|
||||||
|
# min-idle-session: 0
|
||||||
|
# sni: "example.com"
|
||||||
|
# alpn:
|
||||||
|
# - h2
|
||||||
|
# - http/1.1
|
||||||
|
# skip-cert-verify: true
|
||||||
|
|
||||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||||
- name: "dns-out"
|
- name: "dns-out"
|
||||||
type: dns
|
type: dns
|
||||||
@@ -925,6 +965,7 @@ proxy-providers:
|
|||||||
interval: 3600
|
interval: 3600
|
||||||
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir,如果想存储到任意位置,添加环境变量 SKIP_SAFE_PATH_CHECK=1
|
||||||
proxy: DIRECT
|
proxy: DIRECT
|
||||||
|
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||||
header:
|
header:
|
||||||
User-Agent:
|
User-Agent:
|
||||||
- "Clash/v1.18.0"
|
- "Clash/v1.18.0"
|
||||||
@@ -957,6 +998,17 @@ proxy-providers:
|
|||||||
# - pattern: "IPLC-(.*?)倍"
|
# - pattern: "IPLC-(.*?)倍"
|
||||||
# target: "iplc x $1"
|
# target: "iplc x $1"
|
||||||
|
|
||||||
|
provider2:
|
||||||
|
type: inline
|
||||||
|
dialer-proxy: proxy
|
||||||
|
payload:
|
||||||
|
- name: "ss1"
|
||||||
|
type: ss
|
||||||
|
server: server
|
||||||
|
port: 443
|
||||||
|
cipher: chacha20-ietf-poly1305
|
||||||
|
password: "password"
|
||||||
|
|
||||||
test:
|
test:
|
||||||
type: file
|
type: file
|
||||||
path: /test.yaml
|
path: /test.yaml
|
||||||
@@ -972,6 +1024,7 @@ rule-providers:
|
|||||||
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
|
||||||
url: "url"
|
url: "url"
|
||||||
proxy: DIRECT
|
proxy: DIRECT
|
||||||
|
# size-limit: 10240 # 限制下载文件最大为10kb,默认为0即不限制文件大小
|
||||||
rule2:
|
rule2:
|
||||||
behavior: classical
|
behavior: classical
|
||||||
interval: 259200
|
interval: 259200
|
||||||
@@ -995,6 +1048,14 @@ rule-providers:
|
|||||||
format: mrs
|
format: mrs
|
||||||
behavior: domain
|
behavior: domain
|
||||||
path: /path/to/save/file.mrs
|
path: /path/to/save/file.mrs
|
||||||
|
rule4:
|
||||||
|
type: inline
|
||||||
|
behavior: domain # classical / ipcidr
|
||||||
|
payload:
|
||||||
|
- '.blogger.com'
|
||||||
|
- '*.*.microsoft.com'
|
||||||
|
- 'books.itunes.apple.com'
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
- RULE-SET,rule1,REJECT
|
- RULE-SET,rule1,REJECT
|
||||||
- IP-ASN,1,PROXY
|
- IP-ASN,1,PROXY
|
||||||
@@ -1035,7 +1096,7 @@ sub-rules:
|
|||||||
listeners:
|
listeners:
|
||||||
- name: socks5-in-1
|
- name: socks5-in-1
|
||||||
type: socks
|
type: socks
|
||||||
port: 10808
|
port: 10808 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
#listen: 0.0.0.0 # 默认监听 0.0.0.0
|
#listen: 0.0.0.0 # 默认监听 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
|
||||||
@@ -1043,20 +1104,26 @@ listeners:
|
|||||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||||
# - username: aaa
|
# - username: aaa
|
||||||
# password: aaa
|
# password: aaa
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
|
||||||
- name: http-in-1
|
- name: http-in-1
|
||||||
type: http
|
type: http
|
||||||
port: 10809
|
port: 10809 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||||
# - username: aaa
|
# - username: aaa
|
||||||
# password: aaa
|
# password: aaa
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
|
||||||
- name: mixed-in-1
|
- name: mixed-in-1
|
||||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||||
port: 10810
|
port: 10810 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
@@ -1064,17 +1131,20 @@ listeners:
|
|||||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||||
# - username: aaa
|
# - username: aaa
|
||||||
# password: aaa
|
# password: aaa
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
|
||||||
- name: reidr-in-1
|
- name: reidr-in-1
|
||||||
type: redir
|
type: redir
|
||||||
port: 10811
|
port: 10811 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
|
|
||||||
- name: tproxy-in-1
|
- name: tproxy-in-1
|
||||||
type: tproxy
|
type: tproxy
|
||||||
port: 10812
|
port: 10812 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
@@ -1082,7 +1152,7 @@ listeners:
|
|||||||
|
|
||||||
- name: shadowsocks-in-1
|
- name: shadowsocks-in-1
|
||||||
type: shadowsocks
|
type: shadowsocks
|
||||||
port: 10813
|
port: 10813 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
@@ -1091,7 +1161,7 @@ listeners:
|
|||||||
|
|
||||||
- name: vmess-in-1
|
- name: vmess-in-1
|
||||||
type: vmess
|
type: vmess
|
||||||
port: 10814
|
port: 10814 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
@@ -1100,13 +1170,22 @@ listeners:
|
|||||||
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
alterId: 1
|
alterId: 1
|
||||||
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||||
|
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
# certificate: ./server.crt
|
# certificate: ./server.crt
|
||||||
# private-key: ./server.key
|
# private-key: ./server.key
|
||||||
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
|
# reality-config:
|
||||||
|
# dest: test.com:443
|
||||||
|
# private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
|
||||||
|
# short-id:
|
||||||
|
# - 0123456789abcdef
|
||||||
|
# server-names:
|
||||||
|
# - test.com
|
||||||
|
|
||||||
- name: tuic-in-1
|
- name: tuic-in-1
|
||||||
type: tuic
|
type: tuic
|
||||||
port: 10815
|
port: 10815 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
@@ -1126,13 +1205,78 @@ listeners:
|
|||||||
|
|
||||||
- name: tunnel-in-1
|
- name: tunnel-in-1
|
||||||
type: tunnel
|
type: tunnel
|
||||||
port: 10816
|
port: 10816 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
listen: 0.0.0.0
|
listen: 0.0.0.0
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
network: [tcp, udp]
|
network: [tcp, udp]
|
||||||
target: target.com
|
target: target.com
|
||||||
|
|
||||||
|
- name: vless-in-1
|
||||||
|
type: vless
|
||||||
|
port: 10817 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
|
listen: 0.0.0.0
|
||||||
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
|
users:
|
||||||
|
- username: 1
|
||||||
|
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
|
flow: xtls-rprx-vision
|
||||||
|
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||||
|
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
|
reality-config:
|
||||||
|
dest: test.com:443
|
||||||
|
private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
|
||||||
|
short-id:
|
||||||
|
- 0123456789abcdef
|
||||||
|
server-names:
|
||||||
|
- test.com
|
||||||
|
### 注意,对于vless listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 的其中一项 ###
|
||||||
|
|
||||||
|
- name: anytls-in-1
|
||||||
|
type: anytls
|
||||||
|
port: 10818 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
|
listen: 0.0.0.0
|
||||||
|
users:
|
||||||
|
username1: password1
|
||||||
|
username2: password2
|
||||||
|
# "certificate" and "private-key" are required
|
||||||
|
certificate: ./server.crt
|
||||||
|
private-key: ./server.key
|
||||||
|
# padding-scheme: "" # https://github.com/anytls/anytls-go/blob/main/docs/protocol.md#cmdupdatepaddingscheme
|
||||||
|
|
||||||
|
- name: trojan-in-1
|
||||||
|
type: trojan
|
||||||
|
port: 10819 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||||
|
listen: 0.0.0.0
|
||||||
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
|
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||||
|
users:
|
||||||
|
- username: 1
|
||||||
|
password: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
|
||||||
|
# ws-path: "/" # 如果不为空则开启 websocket 传输层
|
||||||
|
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
|
||||||
|
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||||
|
certificate: ./server.crt
|
||||||
|
private-key: ./server.key
|
||||||
|
# 如果填写reality-config则开启reality(注意不可与certificate和private-key同时填写)
|
||||||
|
# reality-config:
|
||||||
|
# dest: test.com:443
|
||||||
|
# private-key: jNXHt1yRo0vDuchQlIP6Z0ZvjT3KtzVI-T4E7RoLJS0 # 可由 mihomo generate reality-keypair 命令生成
|
||||||
|
# short-id:
|
||||||
|
# - 0123456789abcdef
|
||||||
|
# server-names:
|
||||||
|
# - test.com
|
||||||
|
# ss-option: # like trojan-go's `shadowsocks` config
|
||||||
|
# enabled: false
|
||||||
|
# method: aes-128-gcm # aes-128-gcm/aes-256-gcm/chacha20-ietf-poly1305
|
||||||
|
# password: "example"
|
||||||
|
### 注意,对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
|
||||||
|
|
||||||
- name: tun-in-1
|
- name: tun-in-1
|
||||||
type: tun
|
type: tun
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
@@ -1195,4 +1339,4 @@ listeners:
|
|||||||
# authentication-timeout: 1000
|
# authentication-timeout: 1000
|
||||||
# alpn:
|
# alpn:
|
||||||
# - h3
|
# - h3
|
||||||
# max-udp-relay-packet-size: 1500
|
# max-udp-relay-packet-size: 1500
|
||||||
|
|||||||
77
go.mod
77
go.mod
@@ -5,57 +5,60 @@ go 1.20
|
|||||||
require (
|
require (
|
||||||
github.com/3andne/restls-client-go v0.1.6
|
github.com/3andne/restls-client-go v0.1.6
|
||||||
github.com/bahlo/generic-list-go v0.2.0
|
github.com/bahlo/generic-list-go v0.2.0
|
||||||
github.com/coreos/go-iptables v0.7.0
|
github.com/coreos/go-iptables v0.8.0
|
||||||
github.com/dlclark/regexp2 v1.11.4
|
github.com/dlclark/regexp2 v1.11.5
|
||||||
github.com/go-chi/chi/v5 v5.1.0
|
github.com/enfein/mieru/v3 v3.11.2
|
||||||
|
github.com/go-chi/chi/v5 v5.2.1
|
||||||
github.com/go-chi/render v1.0.3
|
github.com/go-chi/render v1.0.3
|
||||||
github.com/gobwas/ws v1.4.0
|
github.com/gobwas/ws v1.4.0
|
||||||
github.com/gofrs/uuid/v5 v5.3.0
|
github.com/gofrs/uuid/v5 v5.3.1
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
|
||||||
github.com/klauspost/compress v1.17.9
|
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8
|
github.com/klauspost/cpuid/v2 v2.2.9
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||||
github.com/mdlayher/netlink v1.7.2
|
github.com/mdlayher/netlink v1.7.2
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
||||||
github.com/metacubex/chacha v0.1.0
|
github.com/metacubex/chacha v0.1.1
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||||
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4
|
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996
|
||||||
github.com/metacubex/randv2 v0.2.0
|
github.com/metacubex/randv2 v0.2.0
|
||||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4
|
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629
|
||||||
|
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8
|
github.com/metacubex/sing-shadowsocks v0.2.8
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1
|
github.com/metacubex/sing-tun v0.4.5
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3
|
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
|
||||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||||
github.com/metacubex/utls v1.6.6
|
github.com/metacubex/utls v1.6.6
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||||
github.com/miekg/dns v1.1.62
|
github.com/miekg/dns v1.1.63
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0
|
github.com/mroth/weightedrand/v2 v2.1.0
|
||||||
github.com/openacid/low v0.1.21
|
github.com/openacid/low v0.1.21
|
||||||
github.com/oschwald/maxminddb-golang v1.12.0
|
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0
|
github.com/puzpuzpuz/xsync/v3 v3.5.0
|
||||||
github.com/sagernet/cors v1.2.1
|
github.com/sagernet/cors v1.2.1
|
||||||
github.com/sagernet/fswatch v0.1.1
|
github.com/sagernet/fswatch v0.1.1
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||||
github.com/sagernet/sing v0.5.0-alpha.13
|
github.com/sagernet/sing v0.5.2
|
||||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
|
github.com/sagernet/sing-mux v0.2.1
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4
|
github.com/sagernet/sing-shadowtls v0.1.5
|
||||||
github.com/samber/lo v1.47.0
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/samber/lo v1.49.1
|
||||||
|
github.com/shirou/gopsutil/v4 v4.25.1
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
github.com/wk8/go-ordered-map/v2 v2.1.8
|
github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||||
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
|
||||||
go.uber.org/automaxprocs v1.6.0
|
go.uber.org/automaxprocs v1.6.0
|
||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
|
||||||
golang.org/x/crypto v0.27.0
|
golang.org/x/crypto v0.33.0
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
|
||||||
golang.org/x/net v0.29.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/sys v0.25.0
|
golang.org/x/sys v0.30.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
lukechampine.com/blake3 v1.3.0
|
lukechampine.com/blake3 v1.3.0
|
||||||
)
|
)
|
||||||
@@ -68,6 +71,7 @@ require (
|
|||||||
github.com/buger/jsonparser v1.1.1 // indirect
|
github.com/buger/jsonparser v1.1.1 // indirect
|
||||||
github.com/cloudflare/circl v1.3.7 // indirect
|
github.com/cloudflare/circl v1.3.7 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/ebitengine/purego v0.8.2 // indirect
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // indirect
|
||||||
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
github.com/ericlagergren/siv v0.0.0-20220507050439-0b757b3aa5f1 // indirect
|
||||||
@@ -78,16 +82,15 @@ require (
|
|||||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/google/btree v1.1.2 // indirect
|
github.com/google/btree v1.1.3 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
|
||||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||||
github.com/josharian/native v1.1.0 // indirect
|
github.com/josharian/native v1.1.0 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/mdlayher/socket v0.4.1 // indirect
|
github.com/mdlayher/socket v0.4.1 // indirect
|
||||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec // indirect
|
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a // indirect
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
github.com/onsi/ginkgo/v2 v2.9.5 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
@@ -96,8 +99,6 @@ require (
|
|||||||
github.com/quic-go/qpack v0.4.0 // indirect
|
github.com/quic-go/qpack v0.4.0 // indirect
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b // indirect
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c // indirect
|
||||||
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
github.com/sina-ghaderi/rabbitio v0.0.0-20220730151941-9ce26f4f872e // indirect
|
||||||
@@ -110,10 +111,10 @@ require (
|
|||||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||||
go.uber.org/mock v0.4.0 // indirect
|
go.uber.org/mock v0.4.0 // indirect
|
||||||
golang.org/x/mod v0.20.0 // indirect
|
golang.org/x/mod v0.20.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.11.0 // indirect
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.7.0 // indirect
|
||||||
golang.org/x/tools v0.24.0 // indirect
|
golang.org/x/tools v0.24.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297
|
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a
|
||||||
|
|||||||
141
go.sum
141
go.sum
@@ -19,14 +19,17 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
|
|||||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||||
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
|
||||||
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
|
||||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
|
||||||
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||||
|
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
|
github.com/enfein/mieru/v3 v3.11.2 h1:06KyGbXiiGz2nSHLJDOOkztAVY3cRr3wBMOpYxPotTo=
|
||||||
|
github.com/enfein/mieru/v3 v3.11.2/go.mod h1:XvVfNsM78lUMSlJJKXJZ0Hn3lAB2o/ETXTbb84x5egw=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||||
@@ -40,8 +43,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
|
|||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
|
||||||
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
|
||||||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
|
||||||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
||||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||||
@@ -56,34 +59,32 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
|||||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
||||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
|
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||||
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
|
||||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||||
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
github.com/google/tink/go v1.6.1 h1:t7JHqO8Ath2w2ig5vjwQYJzhGEZymedQc90lQXUBa4I=
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5 h1:GkMacU5ftc+IEg1449N3UEy2XLDz58W4fkrRu2fibb8=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
|
||||||
github.com/insomniacslk/dhcp v0.0.0-20240812123929-b105c29bd1b5/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
|
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905/go.mod h1:VvGYjkZoJyKqlmT1yzakUs4mfKMNB0XdODP0+rdml6k=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||||
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
|
||||||
@@ -98,40 +99,43 @@ github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31
|
|||||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||||
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
|
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
||||||
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
|
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a h1:cZ6oNVrsmsi3SNlnSnRio4zOgtQq+/XidwsaNgKICcg=
|
||||||
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
|
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a/go.mod h1:xBw/SYJPgUMPQ1tklV/brGn2nxhfr3BnvBzNlyi4Nic=
|
||||||
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4 h1:CgdUBRxmNlxEGkp35HwvgQ10jwOOUJKWdOxpi8yWi8o=
|
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/jBDS/kCYjz/x+0BCOKfd2VODYevyeIt+Ds=
|
||||||
github.com/metacubex/quic-go v0.47.1-0.20240909010619-6b38f24bfcc4/go.mod h1:Y7yRGqFE6UQL/3aKPYmiYdjfVkeujJaStP4+jiZMcN8=
|
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
||||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||||
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297 h1:YG/JkwGPbca5rUtEMHIu8ZuqzR7BSVm1iqY8hNoMeMA=
|
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
|
||||||
github.com/metacubex/sing v0.0.0-20240724044459-6f3cf5896297/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
|
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||||
|
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
|
||||||
|
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1 h1:ypfofGDZbP8p3Y4P/m74JYu7sQViesi3c8nbmT6cS0Y=
|
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
|
||||||
github.com/metacubex/sing-tun v0.2.7-0.20240729131039-ed03f557dee1/go.mod h1:olbEx9yVcaw5tHTNlRamRoxmMKcvDvcVS1YLnQGzvWE=
|
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
||||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
|
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
|
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785 h1:NNmI+ZV0DzNuqaAInRQuZFLHlWVuyHeow8jYpdKjHjo=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20240830120620-c5e019b67785/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
github.com/metacubex/utls v1.6.6 h1:3D12YKHTf2Z41UPhQU2dWerNWJ5TVQD9gKoQ+H+iLC8=
|
||||||
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
github.com/metacubex/utls v1.6.6/go.mod h1:+WLFUnXjcpdxXCnyX25nggw8C6YonZ8zOK2Zm/oRvdo=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||||
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
|
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
|
||||||
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
|
||||||
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
|
||||||
@@ -152,8 +156,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
|
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
github.com/puzpuzpuz/xsync/v3 v3.5.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||||
@@ -166,19 +170,16 @@ github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZN
|
|||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I=
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
|
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
||||||
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
|
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
|
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
|
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||||
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
|
||||||
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b/go.mod h1:X7qrxNQViEaAN9LNZOPl9PfvQtp3V3c7LTo0dvGi0fM=
|
||||||
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
github.com/sina-ghaderi/rabaead v0.0.0-20220730151906-ab6e06b96e8c h1:DjKMC30y6yjG3IxDaeAj3PCoRr+IsO+bzyT+Se2m2Hk=
|
||||||
@@ -197,8 +198,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||||
@@ -230,21 +232,21 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
|||||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
|
||||||
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||||
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -256,19 +258,18 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||||
@@ -277,7 +278,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
|||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
_ "unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter"
|
"github.com/metacubex/mihomo/adapter"
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
@@ -16,9 +17,10 @@ import (
|
|||||||
"github.com/metacubex/mihomo/component/auth"
|
"github.com/metacubex/mihomo/component/auth"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
G "github.com/metacubex/mihomo/component/geodata"
|
"github.com/metacubex/mihomo/component/geodata"
|
||||||
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
mihomoHttp "github.com/metacubex/mihomo/component/http"
|
||||||
"github.com/metacubex/mihomo/component/iface"
|
"github.com/metacubex/mihomo/component/iface"
|
||||||
|
"github.com/metacubex/mihomo/component/keepalive"
|
||||||
"github.com/metacubex/mihomo/component/profile"
|
"github.com/metacubex/mihomo/component/profile"
|
||||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
@@ -100,7 +102,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
updateRules(cfg.Rules, cfg.SubRules, cfg.RuleProviders)
|
||||||
updateSniffer(cfg.Sniffer)
|
updateSniffer(cfg.Sniffer)
|
||||||
updateHosts(cfg.Hosts)
|
updateHosts(cfg.Hosts)
|
||||||
updateGeneral(cfg.General)
|
updateGeneral(cfg.General, true)
|
||||||
updateNTP(cfg.NTP)
|
updateNTP(cfg.NTP)
|
||||||
updateDNS(cfg.DNS, cfg.General.IPv6)
|
updateDNS(cfg.DNS, cfg.General.IPv6)
|
||||||
updateListeners(cfg.General, cfg.Listeners, force)
|
updateListeners(cfg.General, cfg.Listeners, force)
|
||||||
@@ -117,7 +119,7 @@ func ApplyConfig(cfg *config.Config, force bool) {
|
|||||||
runtime.GC()
|
runtime.GC()
|
||||||
tunnel.OnRunning()
|
tunnel.OnRunning()
|
||||||
hcCompatibleProvider(cfg.Providers)
|
hcCompatibleProvider(cfg.Providers)
|
||||||
initExternalUI()
|
updateUpdater(cfg)
|
||||||
|
|
||||||
resolver.ResetConnection()
|
resolver.ResetConnection()
|
||||||
}
|
}
|
||||||
@@ -160,22 +162,25 @@ func GetGeneral() *config.General {
|
|||||||
Interface: dialer.DefaultInterface.Load(),
|
Interface: dialer.DefaultInterface.Load(),
|
||||||
RoutingMark: int(dialer.DefaultRoutingMark.Load()),
|
RoutingMark: int(dialer.DefaultRoutingMark.Load()),
|
||||||
GeoXUrl: config.GeoXUrl{
|
GeoXUrl: config.GeoXUrl{
|
||||||
GeoIp: G.GeoIpUrl(),
|
GeoIp: geodata.GeoIpUrl(),
|
||||||
Mmdb: G.MmdbUrl(),
|
Mmdb: geodata.MmdbUrl(),
|
||||||
ASN: G.ASNUrl(),
|
ASN: geodata.ASNUrl(),
|
||||||
GeoSite: G.GeoSiteUrl(),
|
GeoSite: geodata.GeoSiteUrl(),
|
||||||
},
|
},
|
||||||
GeoAutoUpdate: updater.GeoAutoUpdate(),
|
GeoAutoUpdate: updater.GeoAutoUpdate(),
|
||||||
GeoUpdateInterval: updater.GeoUpdateInterval(),
|
GeoUpdateInterval: updater.GeoUpdateInterval(),
|
||||||
GeodataMode: G.GeodataMode(),
|
GeodataMode: geodata.GeodataMode(),
|
||||||
GeodataLoader: G.LoaderName(),
|
GeodataLoader: geodata.LoaderName(),
|
||||||
GeositeMatcher: G.SiteMatcherName(),
|
GeositeMatcher: geodata.SiteMatcherName(),
|
||||||
TCPConcurrent: dialer.GetTcpConcurrent(),
|
TCPConcurrent: dialer.GetTcpConcurrent(),
|
||||||
FindProcessMode: tunnel.FindProcessMode(),
|
FindProcessMode: tunnel.FindProcessMode(),
|
||||||
Sniffing: tunnel.IsSniffing(),
|
Sniffing: tunnel.IsSniffing(),
|
||||||
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
|
GlobalClientFingerprint: tlsC.GetGlobalFingerprint(),
|
||||||
GlobalUA: mihomoHttp.UA(),
|
GlobalUA: mihomoHttp.UA(),
|
||||||
ETagSupport: resource.ETag(),
|
ETagSupport: resource.ETag(),
|
||||||
|
KeepAliveInterval: int(keepalive.KeepAliveInterval() / time.Second),
|
||||||
|
KeepAliveIdle: int(keepalive.KeepAliveIdle() / time.Second),
|
||||||
|
DisableKeepAlive: keepalive.DisableKeepAlive(),
|
||||||
}
|
}
|
||||||
|
|
||||||
return general
|
return general
|
||||||
@@ -235,6 +240,8 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
resolver.DefaultResolver = nil
|
resolver.DefaultResolver = nil
|
||||||
resolver.DefaultHostMapper = nil
|
resolver.DefaultHostMapper = nil
|
||||||
resolver.DefaultLocalServer = nil
|
resolver.DefaultLocalServer = nil
|
||||||
|
resolver.ProxyServerHostResolver = nil
|
||||||
|
resolver.DirectHostResolver = nil
|
||||||
dns.ReCreateServer("", nil, nil)
|
dns.ReCreateServer("", nil, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -251,11 +258,12 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
Default: c.DefaultNameserver,
|
Default: c.DefaultNameserver,
|
||||||
Policy: c.NameServerPolicy,
|
Policy: c.NameServerPolicy,
|
||||||
ProxyServer: c.ProxyServerNameserver,
|
ProxyServer: c.ProxyServerNameserver,
|
||||||
Tunnel: tunnel.Tunnel,
|
DirectServer: c.DirectNameServer,
|
||||||
|
DirectFollowPolicy: c.DirectFollowPolicy,
|
||||||
CacheAlgorithm: c.CacheAlgorithm,
|
CacheAlgorithm: c.CacheAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
r, pr := dns.NewResolver(cfg)
|
r := dns.NewResolver(cfg)
|
||||||
m := dns.NewEnhancer(cfg)
|
m := dns.NewEnhancer(cfg)
|
||||||
|
|
||||||
// reuse cache of old host mapper
|
// reuse cache of old host mapper
|
||||||
@@ -265,14 +273,22 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
|
|||||||
|
|
||||||
resolver.DefaultResolver = r
|
resolver.DefaultResolver = r
|
||||||
resolver.DefaultHostMapper = m
|
resolver.DefaultHostMapper = m
|
||||||
resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
|
resolver.DefaultLocalServer = dns.NewLocalServer(r.Resolver, m)
|
||||||
resolver.UseSystemHosts = c.UseSystemHosts
|
resolver.UseSystemHosts = c.UseSystemHosts
|
||||||
|
|
||||||
if pr.Invalid() {
|
if r.ProxyResolver.Invalid() {
|
||||||
resolver.ProxyServerHostResolver = pr
|
resolver.ProxyServerHostResolver = r.ProxyResolver
|
||||||
|
} else {
|
||||||
|
resolver.ProxyServerHostResolver = r.Resolver
|
||||||
}
|
}
|
||||||
|
|
||||||
dns.ReCreateServer(c.Listen, r, m)
|
if r.DirectResolver.Invalid() {
|
||||||
|
resolver.DirectHostResolver = r.DirectResolver
|
||||||
|
} else {
|
||||||
|
resolver.DirectHostResolver = r.Resolver
|
||||||
|
}
|
||||||
|
|
||||||
|
dns.ReCreateServer(c.Listen, r.Resolver, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
func updateHosts(tree *trie.DomainTrie[resolver.HostValue]) {
|
||||||
@@ -383,42 +399,63 @@ func updateTunnels(tunnels []LC.Tunnel) {
|
|||||||
listener.PatchTunnel(tunnels, tunnel.Tunnel)
|
listener.PatchTunnel(tunnels, tunnel.Tunnel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func initExternalUI() {
|
func updateUpdater(cfg *config.Config) {
|
||||||
if updater.AutoDownloadUI {
|
general := cfg.General
|
||||||
dirEntries, _ := os.ReadDir(updater.ExternalUIPath)
|
updater.SetGeoAutoUpdate(general.GeoAutoUpdate)
|
||||||
if len(dirEntries) > 0 {
|
updater.SetGeoUpdateInterval(general.GeoUpdateInterval)
|
||||||
log.Infoln("UI already exists, skip downloading")
|
|
||||||
} else {
|
controller := cfg.Controller
|
||||||
log.Infoln("External UI downloading ...")
|
updater.DefaultUiUpdater = updater.NewUiUpdater(controller.ExternalUI, controller.ExternalUIURL, controller.ExternalUIName)
|
||||||
updater.DownloadUI()
|
updater.DefaultUiUpdater.AutoDownloadUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:linkname temporaryUpdateGeneral github.com/metacubex/mihomo/config.temporaryUpdateGeneral
|
||||||
|
func temporaryUpdateGeneral(general *config.General) func() {
|
||||||
|
oldGeneral := GetGeneral()
|
||||||
|
updateGeneral(general, false)
|
||||||
|
return func() {
|
||||||
|
updateGeneral(oldGeneral, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateGeneral(general *config.General) {
|
func updateGeneral(general *config.General, logging bool) {
|
||||||
tunnel.SetMode(general.Mode)
|
tunnel.SetMode(general.Mode)
|
||||||
tunnel.SetFindProcessMode(general.FindProcessMode)
|
tunnel.SetFindProcessMode(general.FindProcessMode)
|
||||||
resolver.DisableIPv6 = !general.IPv6
|
resolver.DisableIPv6 = !general.IPv6
|
||||||
|
|
||||||
if general.TCPConcurrent {
|
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
||||||
dialer.SetTcpConcurrent(general.TCPConcurrent)
|
if logging && general.TCPConcurrent {
|
||||||
log.Infoln("Use tcp concurrent")
|
log.Infoln("Use tcp concurrent")
|
||||||
}
|
}
|
||||||
|
|
||||||
inbound.SetTfo(general.InboundTfo)
|
inbound.SetTfo(general.InboundTfo)
|
||||||
inbound.SetMPTCP(general.InboundMPTCP)
|
inbound.SetMPTCP(general.InboundMPTCP)
|
||||||
|
|
||||||
|
keepalive.SetKeepAliveIdle(time.Duration(general.KeepAliveIdle) * time.Second)
|
||||||
|
keepalive.SetKeepAliveInterval(time.Duration(general.KeepAliveInterval) * time.Second)
|
||||||
|
keepalive.SetDisableKeepAlive(general.DisableKeepAlive)
|
||||||
|
|
||||||
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
adapter.UnifiedDelay.Store(general.UnifiedDelay)
|
||||||
|
|
||||||
dialer.DefaultInterface.Store(general.Interface)
|
dialer.DefaultInterface.Store(general.Interface)
|
||||||
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
dialer.DefaultRoutingMark.Store(int32(general.RoutingMark))
|
||||||
if general.RoutingMark > 0 {
|
if logging && general.RoutingMark > 0 {
|
||||||
log.Infoln("Use routing mark: %#x", general.RoutingMark)
|
log.Infoln("Use routing mark: %#x", general.RoutingMark)
|
||||||
}
|
}
|
||||||
|
|
||||||
iface.FlushCache()
|
iface.FlushCache()
|
||||||
G.SetLoader(general.GeodataLoader)
|
|
||||||
G.SetSiteMatcher(general.GeositeMatcher)
|
geodata.SetGeodataMode(general.GeodataMode)
|
||||||
|
geodata.SetLoader(general.GeodataLoader)
|
||||||
|
geodata.SetSiteMatcher(general.GeositeMatcher)
|
||||||
|
geodata.SetGeoIpUrl(general.GeoXUrl.GeoIp)
|
||||||
|
geodata.SetGeoSiteUrl(general.GeoXUrl.GeoSite)
|
||||||
|
geodata.SetMmdbUrl(general.GeoXUrl.Mmdb)
|
||||||
|
geodata.SetASNUrl(general.GeoXUrl.ASN)
|
||||||
|
mihomoHttp.SetUA(general.GlobalUA)
|
||||||
|
resource.SetETag(general.ETagSupport)
|
||||||
|
|
||||||
|
tlsC.SetGlobalUtlsClient(general.GlobalClientFingerprint)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUsers(users []auth.AuthUser) {
|
func updateUsers(users []auth.AuthUser) {
|
||||||
|
|||||||
@@ -24,9 +24,11 @@ import (
|
|||||||
func configRouter() http.Handler {
|
func configRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getConfigs)
|
r.Get("/", getConfigs)
|
||||||
r.Put("/", updateConfigs)
|
if !embedMode { // disallow update/patch configs in embed mode
|
||||||
r.Post("/geo", updateGeoDatabases)
|
r.Put("/", updateConfigs)
|
||||||
r.Patch("/", patchConfigs)
|
r.Post("/geo", updateGeoDatabases)
|
||||||
|
r.Patch("/", patchConfigs)
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/metacubex/mihomo/tunnel"
|
"github.com/metacubex/mihomo/tunnel"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GroupRouter() http.Handler {
|
func groupRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Get("/", getGroups)
|
r.Get("/", getGroups)
|
||||||
|
|
||||||
|
|||||||
7
hub/route/patch_android.go
Normal file
7
hub/route/patch_android.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build android && cmfa
|
||||||
|
|
||||||
|
package route
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetEmbedMode(true) // set embed mode default
|
||||||
|
}
|
||||||
@@ -36,8 +36,14 @@ var (
|
|||||||
tlsServer *http.Server
|
tlsServer *http.Server
|
||||||
unixServer *http.Server
|
unixServer *http.Server
|
||||||
pipeServer *http.Server
|
pipeServer *http.Server
|
||||||
|
|
||||||
|
embedMode = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SetEmbedMode(embed bool) {
|
||||||
|
embedMode = embed
|
||||||
|
}
|
||||||
|
|
||||||
type Traffic struct {
|
type Traffic struct {
|
||||||
Up int64 `json:"up"`
|
Up int64 `json:"up"`
|
||||||
Down int64 `json:"down"`
|
Down int64 `json:"down"`
|
||||||
@@ -114,14 +120,16 @@ func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
|
|||||||
r.Get("/version", version)
|
r.Get("/version", version)
|
||||||
r.Mount("/configs", configRouter())
|
r.Mount("/configs", configRouter())
|
||||||
r.Mount("/proxies", proxyRouter())
|
r.Mount("/proxies", proxyRouter())
|
||||||
r.Mount("/group", GroupRouter())
|
r.Mount("/group", groupRouter())
|
||||||
r.Mount("/rules", ruleRouter())
|
r.Mount("/rules", ruleRouter())
|
||||||
r.Mount("/connections", connectionRouter())
|
r.Mount("/connections", connectionRouter())
|
||||||
r.Mount("/providers/proxies", proxyProviderRouter())
|
r.Mount("/providers/proxies", proxyProviderRouter())
|
||||||
r.Mount("/providers/rules", ruleProviderRouter())
|
r.Mount("/providers/rules", ruleProviderRouter())
|
||||||
r.Mount("/cache", cacheRouter())
|
r.Mount("/cache", cacheRouter())
|
||||||
r.Mount("/dns", dnsRouter())
|
r.Mount("/dns", dnsRouter())
|
||||||
r.Mount("/restart", restartRouter())
|
if !embedMode { // disallow restart in embed mode
|
||||||
|
r.Mount("/restart", restartRouter())
|
||||||
|
}
|
||||||
r.Mount("/upgrade", upgradeRouter())
|
r.Mount("/upgrade", upgradeRouter())
|
||||||
addExternalRouters(r)
|
addExternalRouters(r)
|
||||||
|
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import (
|
|||||||
|
|
||||||
func upgradeRouter() http.Handler {
|
func upgradeRouter() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/", upgradeCore)
|
|
||||||
r.Post("/ui", updateUI)
|
r.Post("/ui", updateUI)
|
||||||
r.Post("/geo", updateGeoDatabases)
|
if !embedMode { // disallow upgrade core/geo in embed mode
|
||||||
|
r.Post("/", upgradeCore)
|
||||||
|
r.Post("/geo", updateGeoDatabases)
|
||||||
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,7 +49,7 @@ func upgradeCore(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func updateUI(w http.ResponseWriter, r *http.Request) {
|
func updateUI(w http.ResponseWriter, r *http.Request) {
|
||||||
err := updater.DownloadUI()
|
err := updater.DefaultUiUpdater.DownloadUI()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnln("%s", err)
|
log.Warnln("%s", err)
|
||||||
render.Status(r, http.StatusInternalServerError)
|
render.Status(r, http.StatusInternalServerError)
|
||||||
|
|||||||
184
listener/anytls/server.go
Normal file
184
listener/anytls/server.go
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
package anytls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
|
"github.com/metacubex/mihomo/common/atomic"
|
||||||
|
"github.com/metacubex/mihomo/common/buf"
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
|
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||||
|
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||||
|
|
||||||
|
"github.com/sagernet/sing/common/auth"
|
||||||
|
"github.com/sagernet/sing/common/bufio"
|
||||||
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Listener struct {
|
||||||
|
closed bool
|
||||||
|
config LC.AnyTLSServer
|
||||||
|
listeners []net.Listener
|
||||||
|
tlsConfig *tls.Config
|
||||||
|
userMap map[[32]byte]string
|
||||||
|
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||||
|
if len(additions) == 0 {
|
||||||
|
additions = []inbound.Addition{
|
||||||
|
inbound.WithInName("DEFAULT-ANYTLS"),
|
||||||
|
inbound.WithSpecialRules(""),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
|
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
|
sl = &Listener{
|
||||||
|
config: config,
|
||||||
|
tlsConfig: tlsConfig,
|
||||||
|
userMap: make(map[[32]byte]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
for user, password := range config.Users {
|
||||||
|
sl.userMap[sha256.Sum256([]byte(password))] = user
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(config.PaddingScheme) > 0 {
|
||||||
|
if !padding.UpdatePaddingScheme([]byte(config.PaddingScheme), &sl.padding) {
|
||||||
|
return nil, errors.New("incorrect padding scheme format")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &sl.padding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using sing handler can automatically handle UoT
|
||||||
|
h, err := sing.NewListenerHandler(sing.ListenerConfig{
|
||||||
|
Tunnel: tunnel,
|
||||||
|
Type: C.ANYTLS,
|
||||||
|
Additions: additions,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range strings.Split(config.Listen, ",") {
|
||||||
|
addr := addr
|
||||||
|
|
||||||
|
//TCP
|
||||||
|
l, err := inbound.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sl.listeners = append(sl.listeners, l)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
c, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if sl.closed {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go sl.HandleConn(c, h)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return sl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Close() error {
|
||||||
|
l.closed = true
|
||||||
|
var retErr error
|
||||||
|
for _, lis := range l.listeners {
|
||||||
|
err := lis.Close()
|
||||||
|
if err != nil {
|
||||||
|
retErr = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) Config() string {
|
||||||
|
return l.config.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||||
|
for _, lis := range l.listeners {
|
||||||
|
addrList = append(addrList, lis.Addr())
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
||||||
|
ctx := context.TODO()
|
||||||
|
|
||||||
|
conn = tls.Server(conn, l.tlsConfig)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
b := buf.NewPacket()
|
||||||
|
defer b.Release()
|
||||||
|
|
||||||
|
_, err := b.ReadOnceFrom(conn)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn = bufio.NewCachedConn(conn, b)
|
||||||
|
|
||||||
|
by, err := b.ReadBytes(32)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var passwordSha256 [32]byte
|
||||||
|
copy(passwordSha256[:], by)
|
||||||
|
if user, ok := l.userMap[passwordSha256]; ok {
|
||||||
|
ctx = auth.ContextWithUser(ctx, user)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
by, err = b.ReadBytes(2)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paddingLen := binary.BigEndian.Uint16(by)
|
||||||
|
if paddingLen > 0 {
|
||||||
|
_, err = b.ReadBytes(int(paddingLen))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session := session.NewServerSession(conn, func(stream *session.Stream) {
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
destination, err := M.SocksaddrSerializer.ReadAddrPort(stream)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.NewConnection(ctx, stream, M.Metadata{
|
||||||
|
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
|
Destination: destination,
|
||||||
|
})
|
||||||
|
}, &l.padding)
|
||||||
|
session.Run()
|
||||||
|
session.Close()
|
||||||
|
}
|
||||||
19
listener/config/anytls.go
Normal file
19
listener/config/anytls.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnyTLSServer struct {
|
||||||
|
Enable bool `yaml:"enable" json:"enable"`
|
||||||
|
Listen string `yaml:"listen" json:"listen"`
|
||||||
|
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||||
|
Certificate string `yaml:"certificate" json:"certificate"`
|
||||||
|
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||||
|
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t AnyTLSServer) String() string {
|
||||||
|
b, _ := json.Marshal(t)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
16
listener/config/auth.go
Normal file
16
listener/config/auth.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/metacubex/mihomo/component/auth"
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AuthServer for http/socks/mixed server
|
||||||
|
type AuthServer struct {
|
||||||
|
Enable bool
|
||||||
|
Listen string
|
||||||
|
AuthStore auth.AuthStore
|
||||||
|
Certificate string
|
||||||
|
PrivateKey string
|
||||||
|
RealityConfig reality.Config
|
||||||
|
}
|
||||||
@@ -23,6 +23,12 @@ type Hysteria2Server struct {
|
|||||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||||
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,omitempty"`
|
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,omitempty"`
|
||||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||||
|
|
||||||
|
// quic-go special config
|
||||||
|
InitialStreamReceiveWindow uint64 `yaml:"initial-stream-receive-window" json:"initial-stream-receive-window,omitempty"`
|
||||||
|
MaxStreamReceiveWindow uint64 `yaml:"max-stream-receive-window" json:"max-stream-receive-window,omitempty"`
|
||||||
|
InitialConnectionReceiveWindow uint64 `yaml:"initial-connection-receive-window" json:"initial-connection-receive-window,omitempty"`
|
||||||
|
MaxConnectionReceiveWindow uint64 `yaml:"max-connection-receive-window" json:"max-connection-receive-window,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h Hysteria2Server) String() string {
|
func (h Hysteria2Server) String() string {
|
||||||
|
|||||||
38
listener/config/trojan.go
Normal file
38
listener/config/trojan.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TrojanUser struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrojanServer struct {
|
||||||
|
Enable bool
|
||||||
|
Listen string
|
||||||
|
Users []TrojanUser
|
||||||
|
WsPath string
|
||||||
|
GrpcServiceName string
|
||||||
|
Certificate string
|
||||||
|
PrivateKey string
|
||||||
|
RealityConfig reality.Config
|
||||||
|
MuxOption sing.MuxOption
|
||||||
|
TrojanSSOption TrojanSSOption
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||||
|
type TrojanSSOption struct {
|
||||||
|
Enabled bool
|
||||||
|
Method string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t TrojanServer) String() string {
|
||||||
|
b, _ := json.Marshal(t)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
31
listener/config/vless.go
Normal file
31
listener/config/vless.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VlessUser struct {
|
||||||
|
Username string
|
||||||
|
UUID string
|
||||||
|
Flow string
|
||||||
|
}
|
||||||
|
|
||||||
|
type VlessServer struct {
|
||||||
|
Enable bool
|
||||||
|
Listen string
|
||||||
|
Users []VlessUser
|
||||||
|
WsPath string
|
||||||
|
GrpcServiceName string
|
||||||
|
Certificate string
|
||||||
|
PrivateKey string
|
||||||
|
RealityConfig reality.Config
|
||||||
|
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t VlessServer) String() string {
|
||||||
|
b, _ := json.Marshal(t)
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/metacubex/mihomo/listener/sing"
|
|
||||||
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type VmessUser struct {
|
type VmessUser struct {
|
||||||
@@ -13,13 +14,15 @@ type VmessUser struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type VmessServer struct {
|
type VmessServer struct {
|
||||||
Enable bool
|
Enable bool
|
||||||
Listen string
|
Listen string
|
||||||
Users []VmessUser
|
Users []VmessUser
|
||||||
WsPath string
|
WsPath string
|
||||||
Certificate string
|
GrpcServiceName string
|
||||||
PrivateKey string
|
Certificate string
|
||||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
PrivateKey string
|
||||||
|
RealityConfig reality.Config
|
||||||
|
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t VmessServer) String() string {
|
func (t VmessServer) String() string {
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
"github.com/metacubex/mihomo/component/auth"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
authStore "github.com/metacubex/mihomo/listener/auth"
|
authStore "github.com/metacubex/mihomo/listener/auth"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
"github.com/metacubex/mihomo/listener/reality"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
@@ -32,7 +36,7 @@ func (l *Listener) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||||
return NewWithAuthenticator(addr, tunnel, authStore.Default, additions...)
|
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: authStore.Default}, tunnel, additions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWithAuthenticate
|
// NewWithAuthenticate
|
||||||
@@ -40,12 +44,12 @@ func New(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener
|
|||||||
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
|
func NewWithAuthenticate(addr string, tunnel C.Tunnel, authenticate bool, additions ...inbound.Addition) (*Listener, error) {
|
||||||
store := authStore.Default
|
store := authStore.Default
|
||||||
if !authenticate {
|
if !authenticate {
|
||||||
store = authStore.Default
|
store = authStore.Nil
|
||||||
}
|
}
|
||||||
return NewWithAuthenticator(addr, tunnel, store, additions...)
|
return NewWithConfig(LC.AuthServer{Enable: true, Listen: addr, AuthStore: store}, tunnel, additions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, additions ...inbound.Addition) (*Listener, error) {
|
func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.Addition) (*Listener, error) {
|
||||||
isDefault := false
|
isDefault := false
|
||||||
if len(additions) == 0 {
|
if len(additions) == 0 {
|
||||||
isDefault = true
|
isDefault = true
|
||||||
@@ -55,15 +59,42 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
l, err := inbound.Listen("tcp", addr)
|
l, err := inbound.Listen("tcp", config.Listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tlsConfig := &tls.Config{}
|
||||||
|
var realityBuilder *reality.Builder
|
||||||
|
|
||||||
|
if config.Certificate != "" && config.PrivateKey != "" {
|
||||||
|
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
if config.RealityConfig.PrivateKey != "" {
|
||||||
|
if tlsConfig.Certificates != nil {
|
||||||
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
|
}
|
||||||
|
realityBuilder, err = config.RealityConfig.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if realityBuilder != nil {
|
||||||
|
l = realityBuilder.NewListener(l)
|
||||||
|
} else if len(tlsConfig.Certificates) > 0 {
|
||||||
|
l = tls.NewListener(l, tlsConfig)
|
||||||
|
}
|
||||||
|
|
||||||
hl := &Listener{
|
hl := &Listener{
|
||||||
listener: l,
|
listener: l,
|
||||||
addr: addr,
|
addr: config.Listen,
|
||||||
}
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
conn, err := hl.listener.Accept()
|
conn, err := hl.listener.Accept()
|
||||||
@@ -74,7 +105,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
store := store
|
store := config.AuthStore
|
||||||
if isDefault || store == authStore.Default { // only apply on default listener
|
if isDefault || store == authStore.Default { // only apply on default listener
|
||||||
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
|
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|||||||
82
listener/inbound/anytls.go
Normal file
82
listener/inbound/anytls.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/listener/anytls"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AnyTLSOption struct {
|
||||||
|
BaseOption
|
||||||
|
Users map[string]string `inbound:"users,omitempty"`
|
||||||
|
Certificate string `inbound:"certificate"`
|
||||||
|
PrivateKey string `inbound:"private-key"`
|
||||||
|
PaddingScheme string `inbound:"padding-scheme,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o AnyTLSOption) Equal(config C.InboundConfig) bool {
|
||||||
|
return optionToString(o) == optionToString(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnyTLS struct {
|
||||||
|
*Base
|
||||||
|
config *AnyTLSOption
|
||||||
|
l C.MultiAddrListener
|
||||||
|
vs LC.AnyTLSServer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
|
||||||
|
base, err := NewBase(&options.BaseOption)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &AnyTLS{
|
||||||
|
Base: base,
|
||||||
|
config: options,
|
||||||
|
vs: LC.AnyTLSServer{
|
||||||
|
Enable: true,
|
||||||
|
Listen: base.RawAddress(),
|
||||||
|
Users: options.Users,
|
||||||
|
Certificate: options.Certificate,
|
||||||
|
PrivateKey: options.PrivateKey,
|
||||||
|
PaddingScheme: options.PaddingScheme,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config implements constant.InboundListener
|
||||||
|
func (v *AnyTLS) Config() C.InboundConfig {
|
||||||
|
return v.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address implements constant.InboundListener
|
||||||
|
func (v *AnyTLS) Address() string {
|
||||||
|
var addrList []string
|
||||||
|
if v.l != nil {
|
||||||
|
for _, addr := range v.l.AddrList() {
|
||||||
|
addrList = append(addrList, addr.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(addrList, ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen implements constant.InboundListener
|
||||||
|
func (v *AnyTLS) Listen(tunnel C.Tunnel) error {
|
||||||
|
var err error
|
||||||
|
v.l, err = anytls.New(v.vs, tunnel, v.Additions()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Infoln("AnyTLS[%s] proxy listening at: %s", v.Name(), v.Address())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements constant.InboundListener
|
||||||
|
func (v *AnyTLS) Close() error {
|
||||||
|
return v.l.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.InboundListener = (*AnyTLS)(nil)
|
||||||
@@ -5,8 +5,10 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/inbound"
|
"github.com/metacubex/mihomo/adapter/inbound"
|
||||||
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,7 +17,7 @@ type Base struct {
|
|||||||
name string
|
name string
|
||||||
specialRules string
|
specialRules string
|
||||||
listenAddr netip.Addr
|
listenAddr netip.Addr
|
||||||
port int
|
ports utils.IntRanges[uint16]
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBase(options *BaseOption) (*Base, error) {
|
func NewBase(options *BaseOption) (*Base, error) {
|
||||||
@@ -26,11 +28,15 @@ func NewBase(options *BaseOption) (*Base, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ports, err := utils.NewUnsignedRanges[uint16](options.Port)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &Base{
|
return &Base{
|
||||||
name: options.Name(),
|
name: options.Name(),
|
||||||
listenAddr: addr,
|
listenAddr: addr,
|
||||||
specialRules: options.SpecialRules,
|
specialRules: options.SpecialRules,
|
||||||
port: options.Port,
|
ports: ports,
|
||||||
config: options,
|
config: options,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -57,7 +63,15 @@ func (b *Base) Name() string {
|
|||||||
|
|
||||||
// RawAddress implements constant.InboundListener
|
// RawAddress implements constant.InboundListener
|
||||||
func (b *Base) RawAddress() string {
|
func (b *Base) RawAddress() string {
|
||||||
return net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(b.port)))
|
if len(b.ports) == 0 {
|
||||||
|
return net.JoinHostPort(b.listenAddr.String(), "0")
|
||||||
|
}
|
||||||
|
address := make([]string, 0, len(b.ports))
|
||||||
|
b.ports.Range(func(port uint16) bool {
|
||||||
|
address = append(address, net.JoinHostPort(b.listenAddr.String(), strconv.Itoa(int(port))))
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return strings.Join(address, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
@@ -74,7 +88,7 @@ var _ C.InboundListener = (*Base)(nil)
|
|||||||
type BaseOption struct {
|
type BaseOption struct {
|
||||||
NameStr string `inbound:"name"`
|
NameStr string `inbound:"name"`
|
||||||
Listen string `inbound:"listen,omitempty"`
|
Listen string `inbound:"listen,omitempty"`
|
||||||
Port int `inbound:"port,omitempty"`
|
Port string `inbound:"port,omitempty"`
|
||||||
SpecialRules string `inbound:"rule,omitempty"`
|
SpecialRules string `inbound:"rule,omitempty"`
|
||||||
SpecialProxy string `inbound:"proxy,omitempty"`
|
SpecialProxy string `inbound:"proxy,omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/http"
|
"github.com/metacubex/mihomo/listener/http"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPOption struct {
|
type HTTPOption struct {
|
||||||
BaseOption
|
BaseOption
|
||||||
Users AuthUsers `inbound:"users,omitempty"`
|
Users AuthUsers `inbound:"users,omitempty"`
|
||||||
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o HTTPOption) Equal(config C.InboundConfig) bool {
|
func (o HTTPOption) Equal(config C.InboundConfig) bool {
|
||||||
@@ -18,7 +26,7 @@ func (o HTTPOption) Equal(config C.InboundConfig) bool {
|
|||||||
type HTTP struct {
|
type HTTP struct {
|
||||||
*Base
|
*Base
|
||||||
config *HTTPOption
|
config *HTTPOption
|
||||||
l *http.Listener
|
l []*http.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP(options *HTTPOption) (*HTTP, error) {
|
func NewHTTP(options *HTTPOption) (*HTTP, error) {
|
||||||
@@ -39,15 +47,32 @@ func (h *HTTP) Config() C.InboundConfig {
|
|||||||
|
|
||||||
// Address implements constant.InboundListener
|
// Address implements constant.InboundListener
|
||||||
func (h *HTTP) Address() string {
|
func (h *HTTP) Address() string {
|
||||||
return h.l.Address()
|
var addrList []string
|
||||||
|
for _, l := range h.l {
|
||||||
|
addrList = append(addrList, l.Address())
|
||||||
|
}
|
||||||
|
return strings.Join(addrList, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
||||||
var err error
|
for _, addr := range strings.Split(h.RawAddress(), ",") {
|
||||||
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...)
|
l, err := http.NewWithConfig(
|
||||||
if err != nil {
|
LC.AuthServer{
|
||||||
return err
|
Enable: true,
|
||||||
|
Listen: addr,
|
||||||
|
AuthStore: h.config.Users.GetAuthStore(),
|
||||||
|
Certificate: h.config.Certificate,
|
||||||
|
PrivateKey: h.config.PrivateKey,
|
||||||
|
RealityConfig: h.config.RealityConfig.Build(),
|
||||||
|
},
|
||||||
|
tunnel,
|
||||||
|
h.Additions()...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.l = append(h.l, l)
|
||||||
}
|
}
|
||||||
log.Infoln("HTTP[%s] proxy listening at: %s", h.Name(), h.Address())
|
log.Infoln("HTTP[%s] proxy listening at: %s", h.Name(), h.Address())
|
||||||
return nil
|
return nil
|
||||||
@@ -55,8 +80,15 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
|
|||||||
|
|
||||||
// Close implements constant.InboundListener
|
// Close implements constant.InboundListener
|
||||||
func (h *HTTP) Close() error {
|
func (h *HTTP) Close() error {
|
||||||
if h.l != nil {
|
var errs []error
|
||||||
return h.l.Close()
|
for _, l := range h.l {
|
||||||
|
err := l.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/sing_hysteria2"
|
"github.com/metacubex/mihomo/listener/sing_hysteria2"
|
||||||
@@ -23,6 +25,12 @@ type Hysteria2Option struct {
|
|||||||
CWND int `inbound:"cwnd,omitempty"`
|
CWND int `inbound:"cwnd,omitempty"`
|
||||||
UdpMTU int `inbound:"udp-mtu,omitempty"`
|
UdpMTU int `inbound:"udp-mtu,omitempty"`
|
||||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||||
|
|
||||||
|
// quic-go special config
|
||||||
|
InitialStreamReceiveWindow uint64 `inbound:"initial-stream-receive-window,omitempty"`
|
||||||
|
MaxStreamReceiveWindow uint64 `inbound:"max-stream-receive-window,omitempty"`
|
||||||
|
InitialConnectionReceiveWindow uint64 `inbound:"initial-connection-receive-window,omitempty"`
|
||||||
|
MaxConnectionReceiveWindow uint64 `inbound:"max-connection-receive-window,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o Hysteria2Option) Equal(config C.InboundConfig) bool {
|
func (o Hysteria2Option) Equal(config C.InboundConfig) bool {
|
||||||
@@ -61,6 +69,11 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
|
|||||||
CWND: options.CWND,
|
CWND: options.CWND,
|
||||||
UdpMTU: options.UdpMTU,
|
UdpMTU: options.UdpMTU,
|
||||||
MuxOption: options.MuxOption.Build(),
|
MuxOption: options.MuxOption.Build(),
|
||||||
|
// quic-go special config
|
||||||
|
InitialStreamReceiveWindow: options.InitialStreamReceiveWindow,
|
||||||
|
MaxStreamReceiveWindow: options.MaxStreamReceiveWindow,
|
||||||
|
InitialConnectionReceiveWindow: options.InitialConnectionReceiveWindow,
|
||||||
|
MaxConnectionReceiveWindow: options.MaxConnectionReceiveWindow,
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -72,12 +85,13 @@ func (t *Hysteria2) Config() C.InboundConfig {
|
|||||||
|
|
||||||
// Address implements constant.InboundListener
|
// Address implements constant.InboundListener
|
||||||
func (t *Hysteria2) Address() string {
|
func (t *Hysteria2) Address() string {
|
||||||
|
var addrList []string
|
||||||
if t.l != nil {
|
if t.l != nil {
|
||||||
for _, addr := range t.l.AddrList() {
|
for _, addr := range t.l.AddrList() {
|
||||||
return addr.String()
|
addrList = append(addrList, addr.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return strings.Join(addrList, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
|
|||||||
@@ -1,19 +1,24 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/log"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/listener/mixed"
|
"github.com/metacubex/mihomo/listener/mixed"
|
||||||
"github.com/metacubex/mihomo/listener/socks"
|
"github.com/metacubex/mihomo/listener/socks"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MixedOption struct {
|
type MixedOption struct {
|
||||||
BaseOption
|
BaseOption
|
||||||
Users AuthUsers `inbound:"users,omitempty"`
|
Users AuthUsers `inbound:"users,omitempty"`
|
||||||
UDP bool `inbound:"udp,omitempty"`
|
UDP bool `inbound:"udp,omitempty"`
|
||||||
|
Certificate string `inbound:"certificate,omitempty"`
|
||||||
|
PrivateKey string `inbound:"private-key,omitempty"`
|
||||||
|
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o MixedOption) Equal(config C.InboundConfig) bool {
|
func (o MixedOption) Equal(config C.InboundConfig) bool {
|
||||||
@@ -23,8 +28,8 @@ func (o MixedOption) Equal(config C.InboundConfig) bool {
|
|||||||
type Mixed struct {
|
type Mixed struct {
|
||||||
*Base
|
*Base
|
||||||
config *MixedOption
|
config *MixedOption
|
||||||
l *mixed.Listener
|
l []*mixed.Listener
|
||||||
lUDP *socks.UDPListener
|
lUDP []*socks.UDPListener
|
||||||
udp bool
|
udp bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,21 +52,39 @@ func (m *Mixed) Config() C.InboundConfig {
|
|||||||
|
|
||||||
// Address implements constant.InboundListener
|
// Address implements constant.InboundListener
|
||||||
func (m *Mixed) Address() string {
|
func (m *Mixed) Address() string {
|
||||||
return m.l.Address()
|
var addrList []string
|
||||||
|
for _, l := range m.l {
|
||||||
|
addrList = append(addrList, l.Address())
|
||||||
|
}
|
||||||
|
return strings.Join(addrList, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
||||||
var err error
|
for _, addr := range strings.Split(m.RawAddress(), ",") {
|
||||||
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...)
|
l, err := mixed.NewWithConfig(
|
||||||
if err != nil {
|
LC.AuthServer{
|
||||||
return err
|
Enable: true,
|
||||||
}
|
Listen: addr,
|
||||||
if m.udp {
|
AuthStore: m.config.Users.GetAuthStore(),
|
||||||
m.lUDP, err = socks.NewUDP(m.RawAddress(), tunnel, m.Additions()...)
|
Certificate: m.config.Certificate,
|
||||||
|
PrivateKey: m.config.PrivateKey,
|
||||||
|
RealityConfig: m.config.RealityConfig.Build(),
|
||||||
|
},
|
||||||
|
tunnel,
|
||||||
|
m.Additions()...,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.l = append(m.l, l)
|
||||||
|
if m.udp {
|
||||||
|
lUDP, err := socks.NewUDP(addr, tunnel, m.Additions()...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m.lUDP = append(m.lUDP, lUDP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.Infoln("Mixed(http+socks)[%s] proxy listening at: %s", m.Name(), m.Address())
|
log.Infoln("Mixed(http+socks)[%s] proxy listening at: %s", m.Name(), m.Address())
|
||||||
return nil
|
return nil
|
||||||
@@ -69,22 +92,23 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
|
|||||||
|
|
||||||
// Close implements constant.InboundListener
|
// Close implements constant.InboundListener
|
||||||
func (m *Mixed) Close() error {
|
func (m *Mixed) Close() error {
|
||||||
var err error
|
var errs []error
|
||||||
if m.l != nil {
|
for _, l := range m.l {
|
||||||
if tcpErr := m.l.Close(); tcpErr != nil {
|
err := l.Close()
|
||||||
err = tcpErr
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if m.udp && m.lUDP != nil {
|
for _, l := range m.lUDP {
|
||||||
if udpErr := m.lUDP.Close(); udpErr != nil {
|
err := l.Close()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
err = udpErr
|
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
|
||||||
} else {
|
|
||||||
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ C.InboundListener = (*Mixed)(nil)
|
var _ C.InboundListener = (*Mixed)(nil)
|
||||||
|
|||||||
23
listener/inbound/reality.go
Normal file
23
listener/inbound/reality.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package inbound
|
||||||
|
|
||||||
|
import "github.com/metacubex/mihomo/listener/reality"
|
||||||
|
|
||||||
|
type RealityConfig struct {
|
||||||
|
Dest string `inbound:"dest"`
|
||||||
|
PrivateKey string `inbound:"private-key"`
|
||||||
|
ShortID []string `inbound:"short-id"`
|
||||||
|
ServerNames []string `inbound:"server-names"`
|
||||||
|
MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
|
||||||
|
Proxy string `inbound:"proxy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c RealityConfig) Build() reality.Config {
|
||||||
|
return reality.Config{
|
||||||
|
Dest: c.Dest,
|
||||||
|
PrivateKey: c.PrivateKey,
|
||||||
|
ShortID: c.ShortID,
|
||||||
|
ServerNames: c.ServerNames,
|
||||||
|
MaxTimeDifference: c.MaxTimeDifference,
|
||||||
|
Proxy: c.Proxy,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package inbound
|
package inbound
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/listener/redir"
|
"github.com/metacubex/mihomo/listener/redir"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
@@ -17,7 +21,7 @@ func (o RedirOption) Equal(config C.InboundConfig) bool {
|
|||||||
type Redir struct {
|
type Redir struct {
|
||||||
*Base
|
*Base
|
||||||
config *RedirOption
|
config *RedirOption
|
||||||
l *redir.Listener
|
l []*redir.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRedir(options *RedirOption) (*Redir, error) {
|
func NewRedir(options *RedirOption) (*Redir, error) {
|
||||||
@@ -38,15 +42,21 @@ func (r *Redir) Config() C.InboundConfig {
|
|||||||
|
|
||||||
// Address implements constant.InboundListener
|
// Address implements constant.InboundListener
|
||||||
func (r *Redir) Address() string {
|
func (r *Redir) Address() string {
|
||||||
return r.l.Address()
|
var addrList []string
|
||||||
|
for _, l := range r.l {
|
||||||
|
addrList = append(addrList, l.Address())
|
||||||
|
}
|
||||||
|
return strings.Join(addrList, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen implements constant.InboundListener
|
// Listen implements constant.InboundListener
|
||||||
func (r *Redir) Listen(tunnel C.Tunnel) error {
|
func (r *Redir) Listen(tunnel C.Tunnel) error {
|
||||||
var err error
|
for _, addr := range strings.Split(r.RawAddress(), ",") {
|
||||||
r.l, err = redir.New(r.RawAddress(), tunnel, r.Additions()...)
|
l, err := redir.New(addr, tunnel, r.Additions()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
r.l = append(r.l, l)
|
||||||
}
|
}
|
||||||
log.Infoln("Redir[%s] proxy listening at: %s", r.Name(), r.Address())
|
log.Infoln("Redir[%s] proxy listening at: %s", r.Name(), r.Address())
|
||||||
return nil
|
return nil
|
||||||
@@ -54,8 +64,15 @@ func (r *Redir) Listen(tunnel C.Tunnel) error {
|
|||||||
|
|
||||||
// Close implements constant.InboundListener
|
// Close implements constant.InboundListener
|
||||||
func (r *Redir) Close() error {
|
func (r *Redir) Close() error {
|
||||||
if r.l != nil {
|
var errs []error
|
||||||
r.l.Close()
|
for _, l := range r.l {
|
||||||
|
err := l.Close()
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("close redir listener %s err: %w", l.Address(), err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Join(errs...)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user