Compare commits

...

83 Commits

Author SHA1 Message Date
ForestL
8bc6f77e36 fix DEB packaging (#1868) 2025-03-03 11:37:36 +08:00
anytls
a7e56f1c43 fix: anytls client close (#1871)
Co-authored-by: anytls <anytls>
2025-03-02 10:47:10 +08:00
wwqgtxx
05e8f13a8d fix: integer overflow in ports iteration 2025-02-28 15:48:25 +08:00
wwqgtxx
136d114196 feat: socks5/http/mixed inbound support setting tls in listeners 2025-02-28 13:13:53 +08:00
wwqgtxx
938ab7f44d fix: syscall packet read waiter for windows 2025-02-28 12:18:59 +08:00
wwqgtxx
a00f4f1108 fix: vless inbound allow not use flow when request send empty flow 2025-02-28 08:30:36 +08:00
wwqgtxx
1213023f11 fix: reality not work with vmess+grpc outbound 2025-02-28 08:24:22 +08:00
wwqgtxx
3b40bf76b7 fix: grpc server's ALPN order 2025-02-27 22:12:49 +08:00
wwqgtxx
1dc4155195 feat: inbound's port can use ports format 2025-02-27 09:59:09 +08:00
wwqgtxx
d81c19a7c8 fix: grpc server panic 2025-02-26 13:17:26 +08:00
anytls
e2140e62ca chore: update anytls (#1863)
Co-authored-by: anytls <anytls>
2025-02-26 11:17:12 +08:00
wwqgtxx
8d783c65c1 feat: inbound support grpc(lite) 2025-02-26 11:00:11 +08:00
wwqgtxx
91324b76d2 feat: inbound support trojan 2025-02-25 10:30:27 +08:00
wwqgtxx
e23f40a56b chore: tradition shadowsocks server could handle smux 2025-02-24 16:27:20 +08:00
Larvan2
5830afcbde chore: add MinIdleSession option to AnyTLS configuration 2025-02-21 13:30:24 +08:00
anytls
e2b75b35bb chore: update anytls (#1851)
* Implement deadline for `Stream`

* chore: code cleanup

* fix: buffer release

* fix: do not use buffer for `cmdUpdatePaddingScheme`

---------

Co-authored-by: anytls <anytls>
2025-02-19 15:54:56 +08:00
wwqgtxx
06b9e6c367 chore: update dependencies 2025-02-19 08:55:51 +08:00
anytls
dc1145a484 fix: anytls padding send (#1848)
Co-authored-by: anytls <anytls>
2025-02-17 21:18:40 +08:00
wwqgtxx
b151e7d69c chore: support fingerprint for anytls 2025-02-17 20:14:54 +08:00
wwqgtxx
808fdcf624 chore: code cleanup 2025-02-17 19:43:58 +08:00
anytls
9962a0d091 feat: implement anytls client and server (#1844) 2025-02-17 18:51:11 +08:00
ForestL
ef29e4501e chore: complete classical rule parse error log (#1839) 2025-02-13 17:25:45 +08:00
wwqgtxx
d1d846f1ab fix: s390x golang1.24 build 2025-02-13 08:50:53 +08:00
wwqgtxx
eaaccbc6dd chore: update dependencies 2025-02-13 00:34:37 +08:00
wwqgtxx
447c416391 chore: update quic-go to 0.49.0 2025-02-13 00:22:56 +08:00
wwqgtxx
6d24ca9ae6 action: remove 32-bit windows/arm build
windows/arm32 has been broken since Go 1.17
2025-02-12 23:23:05 +08:00
wwqgtxx
b52b7537fc action: force s390x build using golang1.23 2025-02-12 23:22:16 +08:00
wwqgtxx
3cc67fd759 chore: update golang to 1.24 2025-02-12 23:07:05 +08:00
5aaee9
9074b78e36 chore(dns): increase MaxConnsPerHost (#1834) 2025-02-10 15:21:18 +08:00
clash-meta-maintainer[bot]
ccc3f84da2 license: any downstream projects not affiliated with MetaCubeX shall not contain the word mihomo in their names 2025-02-08 23:37:04 +08:00
ForestL
9bfb10d7ae chore: extracting compressed files to correct location (#1823) 2025-02-05 10:10:58 +08:00
wwqgtxx
0a5ea37c07 chore: update dependencies 2025-02-04 15:28:24 +08:00
wwqgtxx
a440f64080 chore: alignment capability for vmess inbound 2025-02-04 15:28:24 +08:00
wwqgtxx
0ac6c3b185 feat: inbound support vless 2025-02-04 00:44:18 +08:00
wwqgtxx
b69e52d4d7 chore: deprecated routing-mark and interface-name of the group, please set it directly on the proxy instead 2025-01-21 00:45:49 +08:00
wwqgtxx
9c73b5b750 fix: the trustcerts not add to globalCerts after ca.ResetCertificate (#1801)
support PEM format for custom-certificates too
2025-01-20 23:01:26 +08:00
wwqgtxx
fc233184fd feat: add receive window config for hy2
https://github.com/MetaCubeX/mihomo/issues/1796
2025-01-19 09:56:16 +08:00
tnextday
192d769f75 chore: ensure forced domains are always sniffed (#1793)
When a domain matches forceDomain:
- SkipList is not checked
- Failed attempts are not cached
- Sniffing is attempted every time

This ensures forced domains are always sniffed regardless of previous failures.
2025-01-16 10:17:32 +08:00
wwqgtxx
c99c71a969 chore: listening tcp together for dns server (#1792) 2025-01-16 10:16:37 +08:00
lucidhz
c7661d7765 fix: initialize error message with cipher (#1760) 2025-01-07 14:28:56 +08:00
Mossia
56c128880c fix: empty proxy provider subscription info not omitted (#1759) 2025-01-07 13:26:56 +08:00
enfein
f4806b49b4 chore: update mieru version (#1762) 2025-01-07 13:25:32 +08:00
J.K.SAGE
49d54cc293 fix: remote conn statistic error (#1776)
TCP handshake traffic should be counted as upload traffic for the remote connection
2025-01-07 13:23:05 +08:00
wwqgtxx
1c5f4a3ab1 chore: update dependencies 2024-12-31 16:42:33 +08:00
wwqgtxx
368b1e1296 chore: rollback tfo-go version 2024-12-30 22:33:13 +08:00
wwqgtxx
a9ce5da09d fix: key missing for tun inbound
https://github.com/MetaCubeX/mihomo/issues/1672
2024-12-28 11:39:45 +08:00
wwqgtxx
301c78ff9a chore: update sing-tun to v0.4.5 2024-12-26 10:50:08 +08:00
wwqgtxx
72a126e580 feat: support inline proxy provider 2024-12-25 10:34:16 +08:00
wwqgtxx
20739f5db7 chore: code cleanup 2024-12-25 10:34:16 +08:00
valord577
89dfabe9b3 chore: align time fields in logs (#1704)
ref: A comma or decimal point followed by one or more zeros represents a fractional second, printed to the given number of decimal places. A comma or decimal point followed by one or more nines represents a fractional second, printed to the given number of decimal places, with trailing zeros removed. For example "15:04:05,000" or "15:04:05.000" formats or parses with millisecond precision.
2024-12-19 15:55:47 +08:00
wwqgtxx
5a9ad0ed3c chore: code cleanup 2024-12-19 09:29:17 +08:00
qianlongzt
bb803249fa feat: support inline rule provider (#1731) 2024-12-19 09:16:45 +08:00
wwqgtxx
3f6823ba49 fix: handle invalid values in Decoder's decode method 2024-12-16 09:26:11 +08:00
wwqgtxx
c786b72030 chore: update dependencies 2024-12-14 13:27:28 +08:00
wwqgtxx
269c52575c chore: update gopsutil to v4 2024-12-14 11:09:51 +08:00
laburaps
c7fc93df37 fix: the TLS Sniffer fails when the length of the ClientHello packet exceeds the TCP MSS (#1711)
* chore: add uniformly formatted debug info to sniffDomain

* fix: when data is not enough, attempt to peek more data and retry

* chore: reduce debug info of sniffDomain
2024-12-12 19:02:34 +08:00
laburaps
5d9d8f4d3b fix: check whether the dst port is within the specified range (#1706) 2024-12-10 16:15:08 +08:00
wwqgtxx
f3a43fe3a6 feat: support read config file from stdin
via `-f -`
2024-12-10 09:57:20 +08:00
wwqgtxx
9a959202ed chore: support config multiplexing of mieru 2024-12-10 09:19:59 +08:00
enfein
cd23112dc5 chore: remove gRPC dependency from mieru (#1705) 2024-12-10 08:03:17 +08:00
enfein
613becd8ea feat: support mieru protocol (#1702) 2024-12-09 12:05:11 +08:00
hingbong
d6b496d3c0 chore: allow upgrade ui in embed mode (#1692) 2024-12-04 08:54:01 +08:00
ForestL
5a24efdabf fix: DisableKeepAlive default value of android (#1690) 2024-12-02 22:49:16 +08:00
wwqgtxx
9de9f1ef51 fix: don't panic when listen on localhost
https://github.com/MetaCubeX/mihomo/issues/1655
2024-11-27 11:03:38 +08:00
wwqgtxx
fbead56ec9 feat: add size-limit for provider
https://github.com/MetaCubeX/mihomo/issues/1645
2024-11-27 09:28:38 +08:00
wwqgtxx
1fff34d30e chore: update quic-go to 0.48.2 2024-11-26 13:39:54 +08:00
wwqgtxx
a35f712478 chore: update gvisor 2024-11-26 10:28:07 +08:00
wwqgtxx
f805a9f4c6 chore: cleaned up some weird code 2024-11-26 10:04:41 +08:00
xishang0128
eb985b002e chore: restful api displays more information 2024-11-21 22:50:54 +08:00
wwqgtxx
462343531e chore: update sing-tun to v0.4.1 2024-11-21 11:06:25 +08:00
wwqgtxx
671d901ee2 ci: align loongarch golang version when it is not abi1 2024-11-18 10:41:15 +08:00
wwqgtxx
80e4eaad14 fix: process IPv6 Link-Local address (#1657) 2024-11-18 10:34:43 +08:00
xishang0128
25b3c86d31 ci: update loongarch golang and android ndk 2024-11-17 23:31:46 +08:00
Chenx Dust
de19f927e8 chore: restful api display smux and mptcp 2024-11-14 10:08:02 +08:00
Larvan2
792f16265e fix: find process panic 2024-11-08 16:29:32 +08:00
wwqgtxx
215bf0995f chore: switch syscall.SyscallN back to syscall.Syscall6
Until the current version, SyscallN always escapes the variadic argument
2024-11-08 09:40:38 +08:00
wwqgtxx
91d54bdac1 fix: android tun start error 2024-11-06 20:04:14 +08:00
wwqgtxx
ce52c3438b chore: cleaned up some confusing code 2024-11-05 10:03:21 +08:00
wwqgtxx
d4478dbfa2 chore: reduce the performance overhead of not enabling LoopBackDetector 2024-11-05 09:29:56 +08:00
wwqgtxx
69454b030e chore: allow disabled overrideAndroidVPN by environment variable DISABLE_OVERRIDE_ANDROID_VPN 2024-11-05 09:15:30 +08:00
wwqgtxx
e6d1c8cedf chore: update sing-tun to v0.4.0-rc.5 2024-11-05 09:12:20 +08:00
wwqgtxx
fabd216c34 chore: update quic-go to 0.48.1 2024-11-05 08:58:41 +08:00
xishang0128
a86c562852 chore: Increase support for other format of ASN 2024-11-04 19:31:43 +08:00
141 changed files with 6429 additions and 933 deletions

View File

@@ -1,17 +1,17 @@
[Unit]
Description=mihomo Daemon, Another Clash Kernel.
After=network.target NetworkManager.service systemd-networkd.service iwd.service
Documentation=https://wiki.metacubex.one
After=network.target nss-lookup.target network-online.target
[Service]
Type=simple
LimitNPROC=500
LimitNOFILE=1000000
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH 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
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
ExecStart=/usr/bin/mihomo -d /etc/mihomo
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
LimitNOFILE=infinity
[Install]
WantedBy=multi-user.target
WantedBy=multi-user.target

17
.github/mihomo@.service vendored Normal file
View 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

View File

@@ -54,7 +54,6 @@ jobs:
- { goos: windows, goarch: '386', output: '386' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64 }
- { goos: windows, goarch: arm, goarm: '7', output: armv7 }
- { goos: windows, goarch: arm64, output: arm64 }
- { goos: freebsd, goarch: '386', output: '386' }
@@ -67,6 +66,12 @@ jobs:
- { goos: android, goarch: arm, ndk: armv7a-linux-androideabi34, output: armv7 }
- { goos: android, goarch: arm64, ndk: aarch64-linux-android34, output: arm64-v8 }
# Go 1.23 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
- { goos: windows, goarch: '386', output: '386-go123', goversion: '1.23' }
- { goos: windows, goarch: amd64, goamd64: v1, output: amd64-compatible-go123, goversion: '1.23' }
- { goos: windows, goarch: amd64, goamd64: v3, output: amd64-go123, goversion: '1.23' }
# Go 1.22 with special patch can work on Windows 7
# https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
- { goos: windows, goarch: '386', output: '386-go122', goversion: '1.22' }
@@ -95,6 +100,11 @@ jobs:
- { goos: darwin, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20' }
- { goos: darwin, goarch: amd64, goamd64: v3, output: amd64-go120, goversion: '1.20' }
# Go 1.23 is the last release that requires Linux kernel version 2.6.32 or later. Go 1.24 will require Linux kernel version 3.2 or later.
- { goos: linux, goarch: '386', output: '386-go123', goversion: '1.23' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go123, goversion: '1.23', test: test }
- { goos: linux, goarch: amd64, goamd64: v3, output: amd64-go123, goversion: '1.23' }
# only for test
- { goos: linux, goarch: '386', output: '386-go120', goversion: '1.20' }
- { goos: linux, goarch: amd64, goamd64: v1, output: amd64-compatible-go120, goversion: '1.20', test: test }
@@ -104,30 +114,41 @@ jobs:
- uses: actions/checkout@v4
- name: Set up Go
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.goarch != 'loong64' }}
if: ${{ matrix.jobs.goversion == '' && matrix.jobs.abi != '1' }}
uses: actions/setup-go@v5
with:
go-version: '1.23'
go-version: '1.24'
- name: Set up Go
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.goarch != 'loong64' }}
if: ${{ matrix.jobs.goversion != '' && matrix.jobs.abi != '1' }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.jobs.goversion }}
- name: Set up Go1.22 loongarch abi1
- name: Set up Go1.23 loongarch abi1
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '1' }}
run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi1.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi1.tar.gz -C /usr/local
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.23.0/go1.23.0.linux-amd64-abi1.tar.gz
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
- name: Set up Go1.22 loongarch abi2
if: ${{ matrix.jobs.goarch == 'loong64' && matrix.jobs.abi == '2' }}
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.24.x
# that means after golang1.25 release it must be changed
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.24/
# revert:
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.24 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
run: |
wget -q https://github.com/MetaCubeX/loongarch64-golang/releases/download/1.22.4/go1.22.4.linux-amd64-abi2.tar.gz
sudo tar zxf go1.22.4.linux-amd64-abi2.tar.gz -C /usr/local
echo "/usr/local/go/bin" >> $GITHUB_PATH
cd $(go env GOROOT)
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/979d6d8bab3823ff572ace26767fd2ce3cf351ae.diff | patch --verbose -p 1
curl https://github.com/MetaCubeX/go/commit/ac3e93c061779dfefc0dd13a5b6e6f764a25621e.diff | patch --verbose -p 1
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
# this patch file only works on golang1.23.x
@@ -139,7 +160,7 @@ jobs:
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
- name: Revert Golang1.23 commit for Windows7/8
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '' }}
if: ${{ matrix.jobs.goos == 'windows' && matrix.jobs.goversion == '1.23' }}
run: |
cd $(go env GOROOT)
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
@@ -194,7 +215,7 @@ jobs:
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r27
ndk-version: r28-beta1
- name: Set NDK path
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}/usr/bin
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo
mkdir -p mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/mihomo
cp mihomo mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/bin/
cp LICENSE mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/usr/share/licenses/mihomo/
cp .github/mihomo.service mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/systemd/system/
cp .github/{mihomo.service,mihomo@.service} mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/lib/systemd/system/
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/etc/mihomo/config.yaml <<EOF
mixed-port: 7890
external-controller: 127.0.0.1:9090
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/conffiles <<EOF
/etc/mihomo/config.yaml
EOF
cat > mihomo-${{matrix.jobs.goos}}-${{matrix.jobs.output}}-${VERSION}/DEBIAN/control <<EOF

View File

@@ -98,3 +98,4 @@ API.
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.**

View File

@@ -163,8 +163,17 @@ func (p *Proxy) MarshalJSON() ([]byte, error) {
mapping["alive"] = p.alive.Load()
mapping["name"] = p.Name()
mapping["udp"] = p.SupportUDP()
mapping["xudp"] = p.SupportXUDP()
mapping["tfo"] = p.SupportTFO()
mapping["uot"] = p.SupportUOT()
proxyInfo := p.ProxyInfo()
mapping["xudp"] = proxyInfo.XUDP
mapping["tfo"] = proxyInfo.TFO
mapping["mptcp"] = proxyInfo.MPTCP
mapping["smux"] = proxyInfo.SMUX
mapping["interface"] = proxyInfo.Interface
mapping["dialer-proxy"] = proxyInfo.DialerProxy
mapping["routing-mark"] = proxyInfo.RoutingMark
return json.Marshal(mapping)
}

View File

@@ -34,12 +34,5 @@ func SkipAuthRemoteAddress(addr string) bool {
}
func skipAuth(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range skipAuthPrefixes {
if prefix.Contains(addr.Unmap()) {
return true
}
}
}
return false
return prefixesContains(skipAuthPrefixes, addr)
}

View File

@@ -31,27 +31,17 @@ func IsRemoteAddrDisAllowed(addr net.Addr) bool {
if err := m.SetRemoteAddr(addr); err != nil {
return false
}
return isAllowed(m.AddrPort().Addr().Unmap()) && !isDisAllowed(m.AddrPort().Addr().Unmap())
ipAddr := m.AddrPort().Addr()
if ipAddr.IsValid() {
return isAllowed(ipAddr) && !isDisAllowed(ipAddr)
}
return false
}
func isAllowed(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range lanAllowedIPs {
if prefix.Contains(addr) {
return true
}
}
}
return false
return prefixesContains(lanAllowedIPs, addr)
}
func isDisAllowed(addr netip.Addr) bool {
if addr.IsValid() {
for _, prefix := range lanDisAllowedIPs {
if prefix.Contains(addr) {
return true
}
}
}
return false
return prefixesContains(lanDisAllowedIPs, addr)
}

View File

@@ -2,7 +2,9 @@ package inbound
import (
"context"
"fmt"
"net"
"net/netip"
"sync"
"github.com/metacubex/mihomo/component/keepalive"
@@ -41,7 +43,36 @@ func MPTCP() bool {
return getMultiPathTCP(&lc.ListenConfig)
}
func preResolve(network, address string) (string, error) {
switch network { // like net.Resolver.internetAddrList but filter domain to avoid call net.Resolver.lookupIPAddr
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip", "ip4", "ip6":
if host, port, err := net.SplitHostPort(address); err == nil {
switch host {
case "localhost":
switch network {
case "tcp6", "udp6", "ip6":
address = net.JoinHostPort("::1", port)
default:
address = net.JoinHostPort("127.0.0.1", port)
}
case "": // internetAddrList can handle this special case
break
default:
if _, err := netip.ParseAddr(host); err != nil { // not ip
return "", fmt.Errorf("invalid network address: %s", address)
}
}
}
}
return address, nil
}
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
address, err := preResolve(network, address)
if err != nil {
return nil, err
}
mutex.RLock()
defer mutex.RUnlock()
return lc.Listen(ctx, network, address)
@@ -51,6 +82,21 @@ func Listen(network, address string) (net.Listener, error) {
return ListenContext(context.Background(), network, address)
}
func ListenPacketContext(ctx context.Context, network, address string) (net.PacketConn, error) {
address, err := preResolve(network, address)
if err != nil {
return nil, err
}
mutex.RLock()
defer mutex.RUnlock()
return lc.ListenPacket(ctx, network, address)
}
func ListenPacket(network, address string) (net.PacketConn, error) {
return ListenPacketContext(context.Background(), network, address)
}
func init() {
keepalive.SetDisableKeepAliveCallback.Register(func(b bool) {
mutex.Lock()

View File

@@ -61,3 +61,19 @@ func parseHTTPAddr(request *http.Request) *C.Metadata {
return metadata
}
func prefixesContains(prefixes []netip.Prefix, addr netip.Addr) bool {
if len(prefixes) == 0 {
return false
}
if !addr.IsValid() {
return false
}
addr = addr.Unmap().WithZone("") // netip.Prefix.Contains returns false if ip has an IPv6 zone
for _, prefix := range prefixes {
if prefix.Contains(addr) {
return true
}
}
return false
}

140
adapter/outbound/anytls.go Normal file
View 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
}

View File

@@ -85,14 +85,15 @@ func (b *Base) SupportUDP() bool {
return b.udp
}
// SupportXUDP implements C.ProxyAdapter
func (b *Base) SupportXUDP() bool {
return b.xudp
}
// SupportTFO implements C.ProxyAdapter
func (b *Base) SupportTFO() bool {
return b.tfo
// ProxyInfo implements C.ProxyAdapter
func (b *Base) ProxyInfo() (info C.ProxyInfo) {
info.XUDP = b.xudp
info.TFO = b.tfo
info.MPTCP = b.mpTcp
info.SMUX = false
info.Interface = b.iface
info.RoutingMark = b.rmark
return
}
// IsL3Protocol implements C.ProxyAdapter
@@ -152,11 +153,11 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
}
type BasicOption struct {
TFO bool `proxy:"tfo,omitempty" group:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty" group:"mptcp,omitempty"`
TFO bool `proxy:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty"`
Interface string `proxy:"interface-name,omitempty" group:"interface-name,omitempty"`
RoutingMark int `proxy:"routing-mark,omitempty" group:"routing-mark,omitempty"`
IPVersion string `proxy:"ip-version,omitempty" group:"ip-version,omitempty"`
IPVersion string `proxy:"ip-version,omitempty"`
DialerProxy string `proxy:"dialer-proxy,omitempty"` // don't apply this option into groups, but can set a group name in a proxy
}

View File

@@ -3,18 +3,12 @@ package outbound
import (
"context"
"errors"
"os"
"strconv"
"github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/loopback"
"github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
)
var DisableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
type Direct struct {
*Base
loopBack *loopback.Detector
@@ -27,10 +21,8 @@ type DirectOption struct {
// DialContext implements C.ProxyAdapter
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 {
return nil, err
}
if err := d.loopBack.CheckConn(metadata); err != nil {
return nil, err
}
opts = append(opts, dialer.WithResolver(resolver.DirectHostResolver))
c, err := dialer.DialContext(ctx, "tcp", metadata.RemoteAddress(), d.Base.DialOptions(opts...)...)
@@ -42,10 +34,8 @@ func (d *Direct) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
// ListenPacketContext implements C.ProxyAdapter
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 {
return nil, err
}
if err := d.loopBack.CheckPacketConn(metadata); err != nil {
return nil, err
}
// net.UDPConn.WriteTo only working with *net.UDPAddr, so we need a net.UDPAddr
if !metadata.Resolved() {

View File

@@ -92,6 +92,13 @@ func (h *Http) SupportWithDialer() C.NetWork {
return C.TCP
}
// ProxyInfo implements C.ProxyAdapter
func (h *Http) ProxyInfo() C.ProxyInfo {
info := h.Base.ProxyInfo()
info.DialerProxy = h.option.DialerProxy
return info
}
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
addr := metadata.RemoteAddress()
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"

View File

@@ -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 {
BasicOption
Name string `proxy:"name"`

View File

@@ -21,6 +21,7 @@ import (
"github.com/metacubex/sing-quic/hysteria2"
"github.com/metacubex/quic-go"
"github.com/metacubex/randv2"
M "github.com/sagernet/sing/common/metadata"
)
@@ -62,6 +63,12 @@ type Hysteria2Option struct {
CustomCAString string `proxy:"ca-str,omitempty"`
CWND int `proxy:"cwnd,omitempty"`
UdpMTU int `proxy:"udp-mtu,omitempty"`
// quic-go special config
InitialStreamReceiveWindow uint64 `proxy:"initial-stream-receive-window,omitempty"`
MaxStreamReceiveWindow uint64 `proxy:"max-stream-receive-window,omitempty"`
InitialConnectionReceiveWindow uint64 `proxy:"initial-connection-receive-window,omitempty"`
MaxConnectionReceiveWindow uint64 `proxy:"max-connection-receive-window,omitempty"`
}
func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
@@ -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) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
var salamanderPassword string
@@ -138,6 +152,13 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
option.UdpMTU = 1200 - 3
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: option.InitialStreamReceiveWindow,
MaxStreamReceiveWindow: option.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: option.InitialConnectionReceiveWindow,
MaxConnectionReceiveWindow: option.MaxConnectionReceiveWindow,
}
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
clientOptions := hysteria2.ClientOptions{
@@ -149,6 +170,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
SalamanderPassword: salamanderPassword,
Password: option.Password,
TLSConfig: tlsConfig,
QUICConfig: quicConfig,
UDPDisabled: false,
CWND: option.CWND,
UdpMTU: option.UdpMTU,

281
adapter/outbound/mieru.go Normal file
View 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
}

View 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)
}
}
}
}

View File

@@ -196,6 +196,13 @@ func (ss *ShadowSocks) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (ss *ShadowSocks) ProxyInfo() C.ProxyInfo {
info := ss.Base.ProxyInfo()
info.DialerProxy = ss.option.DialerProxy
return info
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (ss *ShadowSocks) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
if ss.option.UDPOverTCP {
@@ -229,7 +236,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
Password: option.Password,
})
if err != nil {
return nil, fmt.Errorf("ss %s initialize error: %w", addr, err)
return nil, fmt.Errorf("ss %s cipher: %s initialize error: %w", addr, option.Cipher, err)
}
var v2rayOption *v2rayObfs.Option

View File

@@ -122,6 +122,13 @@ func (ssr *ShadowSocksR) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (ssr *ShadowSocksR) ProxyInfo() C.ProxyInfo {
info := ssr.Base.ProxyInfo()
info.DialerProxy = ssr.option.DialerProxy
return info
}
func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
// SSR protocol compatibility
// https://github.com/metacubex/mihomo/pull/2056
@@ -134,7 +141,7 @@ func NewShadowSocksR(option ShadowSocksROption) (*ShadowSocksR, error) {
password := option.Password
coreCiph, err := core.PickCipher(cipher, nil, password)
if err != nil {
return nil, fmt.Errorf("ssr %s initialize error: %w", addr, err)
return nil, fmt.Errorf("ssr %s cipher: %s initialize error: %w", addr, cipher, err)
}
var (
ivSize int

View File

@@ -97,6 +97,12 @@ func (s *SingMux) SupportUOT() bool {
return true
}
func (s *SingMux) ProxyInfo() C.ProxyInfo {
info := s.ProxyAdapter.ProxyInfo()
info.SMUX = true
return info
}
func closeSingMux(s *SingMux) {
_ = s.client.Close()
}

View File

@@ -141,6 +141,13 @@ func (s *Snell) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (s *Snell) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
func NewSnell(option SnellOption) (*Snell, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
psk := []byte(option.Psk)
@@ -204,7 +211,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
if err != nil {
return nil, err
}
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
})
}

View File

@@ -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
}
// ProxyInfo implements C.ProxyAdapter
func (ss *Socks5) ProxyInfo() C.ProxyInfo {
info := ss.Base.ProxyInfo()
info.DialerProxy = ss.option.DialerProxy
return info
}
func NewSocks5(option Socks5Option) (*Socks5, error) {
var tlsConfig *tls.Config
if option.TLS {

View File

@@ -121,6 +121,13 @@ func closeSsh(s *Ssh) {
_ = s.client.Close()
}
// ProxyInfo implements C.ProxyAdapter
func (s *Ssh) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
func NewSsh(option SshOption) (*Ssh, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))

View File

@@ -244,6 +244,13 @@ func (t *Trojan) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (t *Trojan) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}
func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))

View File

@@ -146,6 +146,13 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
return
}
// ProxyInfo implements C.ProxyAdapter
func (t *Tuic) ProxyInfo() C.ProxyInfo {
info := t.Base.ProxyInfo()
info.DialerProxy = t.option.DialerProxy
return info
}
func NewTuic(option TuicOption) (*Tuic, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
serverName := option.Server

View File

@@ -21,7 +21,6 @@ import (
"github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mihomo/transport/vless"
@@ -379,6 +378,13 @@ func (v *Vless) SupportUOT() bool {
return true
}
// ProxyInfo implements C.ProxyAdapter
func (v *Vless) ProxyInfo() C.ProxyInfo {
info := v.Base.ProxyInfo()
info.DialerProxy = v.option.DialerProxy
return info
}
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
var addrType byte
var addr []byte
@@ -506,8 +512,6 @@ func NewVless(option VlessOption) (*Vless, error) {
if option.Flow != vless.XRV {
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{
Flow: option.Flow,
}

View File

@@ -388,6 +388,13 @@ func (v *Vmess) SupportWithDialer() C.NetWork {
return C.ALLNet
}
// ProxyInfo implements C.ProxyAdapter
func (v *Vmess) ProxyInfo() C.ProxyInfo {
info := v.Base.ProxyInfo()
info.DialerProxy = v.option.DialerProxy
return info
}
// ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
@@ -452,6 +459,11 @@ func NewVmess(option VmessOption) (*Vmess, error) {
option: &option,
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
switch option.Network {
case "h2":
if len(option.HTTP2Opts.Host) == 0 {
@@ -500,11 +512,6 @@ func NewVmess(option VmessOption) (*Vmess, error) {
v.transport = gun.NewHTTP2Client(dialFn, tlsConfig, v.option.ClientFingerprint, v.realityConfig)
}
v.realityConfig, err = v.option.RealityOpts.Parse()
if err != nil {
return nil, err
}
return v, nil
}

View File

@@ -611,18 +611,11 @@ func (r *refProxyAdapter) SupportUDP() bool {
return false
}
func (r *refProxyAdapter) SupportXUDP() bool {
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportXUDP()
return r.proxyAdapter.ProxyInfo()
}
return false
}
func (r *refProxyAdapter) SupportTFO() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportTFO()
}
return false
return C.ProxyInfo{}
}
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {

View File

@@ -46,6 +46,13 @@ type GroupBaseOption struct {
}
func NewGroupBase(opt GroupBaseOption) *GroupBase {
if opt.RoutingMark != 0 {
log.Warnln("The group [%s] with routing-mark configuration is deprecated, please set it directly on the proxy instead", opt.Name)
}
if opt.Interface != "" {
log.Warnln("The group [%s] with interface-name configuration is deprecated, please set it directly on the proxy instead", opt.Name)
}
var excludeFilterReg *regexp2.Regexp
if opt.excludeFilter != "" {
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)

View File

@@ -141,6 +141,20 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
break
}
proxy, err = outbound.NewSsh(*sshOption)
case "mieru":
mieruOption := &outbound.MieruOption{}
err = decoder.Decode(mapping, mieruOption)
if err != nil {
break
}
proxy, err = outbound.NewMieru(*mieruOption)
case "anytls":
anytlsOption := &outbound.AnyTLSOption{}
err = decoder.Decode(mapping, anytlsOption)
if err != nil {
break
}
proxy, err = outbound.NewAnyTLS(*anytlsOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}

View File

@@ -57,15 +57,17 @@ type OverrideSchema struct {
}
type proxyProviderSchema struct {
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
Type string `provider:"type"`
Path string `provider:"path,omitempty"`
URL string `provider:"url,omitempty"`
Proxy string `provider:"proxy,omitempty"`
Interval int `provider:"interval,omitempty"`
Filter string `provider:"filter,omitempty"`
ExcludeFilter string `provider:"exclude-filter,omitempty"`
ExcludeType string `provider:"exclude-type,omitempty"`
DialerProxy string `provider:"dialer-proxy,omitempty"`
SizeLimit int64 `provider:"size-limit,omitempty"`
Payload []map[string]any `provider:"payload,omitempty"`
HealthCheck healthCheckSchema `provider:"health-check,omitempty"`
Override OverrideSchema `provider:"override,omitempty"`
@@ -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)
parser, err := NewProxiesParser(schema.Filter, schema.ExcludeFilter, schema.ExcludeType, schema.DialerProxy, schema.Override)
if err != nil {
return nil, err
}
var vehicle types.Vehicle
switch schema.Type {
case "file":
@@ -111,17 +118,14 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
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:
return nil, fmt.Errorf("%w: %s", errVehicleType, schema.Type)
}
interval := time.Duration(uint(schema.Interval)) * time.Second
filter := schema.Filter
excludeFilter := schema.ExcludeFilter
excludeType := schema.ExcludeType
dialerProxy := schema.DialerProxy
override := schema.Override
return NewProxySetProvider(name, interval, filter, excludeFilter, excludeType, dialerProxy, override, vehicle, hc)
return NewProxySetProvider(name, interval, parser, vehicle, hc)
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
@@ -30,44 +31,101 @@ type ProxySchema struct {
Proxies []map[string]any `yaml:"proxies"`
}
type providerForApi struct {
Name string `json:"name"`
Type string `json:"type"`
VehicleType string `json:"vehicleType"`
Proxies []C.Proxy `json:"proxies"`
TestUrl string `json:"testUrl"`
ExpectedStatus string `json:"expectedStatus"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
SubscriptionInfo *SubscriptionInfo `json:"subscriptionInfo,omitempty"`
}
type baseProvider struct {
name string
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
}
func (bp *baseProvider) Name() string {
return bp.name
}
func (bp *baseProvider) Version() uint32 {
return bp.version
}
func (bp *baseProvider) HealthCheck() {
bp.healthCheck.check()
}
func (bp *baseProvider) Type() types.ProviderType {
return types.Proxy
}
func (bp *baseProvider) Proxies() []C.Proxy {
return bp.proxies
}
func (bp *baseProvider) Count() int {
return len(bp.proxies)
}
func (bp *baseProvider) Touch() {
bp.healthCheck.touch()
}
func (bp *baseProvider) HealthCheckURL() string {
return bp.healthCheck.url
}
func (bp *baseProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
bp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
}
func (bp *baseProvider) setProxies(proxies []C.Proxy) {
bp.proxies = proxies
bp.healthCheck.setProxy(proxies)
if bp.healthCheck.auto() {
go bp.healthCheck.check()
}
}
func (bp *baseProvider) Close() error {
bp.healthCheck.close()
return nil
}
// ProxySetProvider for auto gc
type ProxySetProvider struct {
*proxySetProvider
}
type proxySetProvider struct {
baseProvider
*resource.Fetcher[[]C.Proxy]
proxies []C.Proxy
healthCheck *HealthCheck
version uint32
subscriptionInfo *SubscriptionInfo
}
func (pp *proxySetProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": pp.Name(),
"type": pp.Type().String(),
"vehicleType": pp.VehicleType().String(),
"proxies": pp.Proxies(),
"testUrl": pp.healthCheck.url,
"expectedStatus": pp.healthCheck.expectedStatus.String(),
"updatedAt": pp.UpdatedAt(),
"subscriptionInfo": pp.subscriptionInfo,
return json.Marshal(providerForApi{
Name: pp.Name(),
Type: pp.Type().String(),
VehicleType: pp.VehicleType().String(),
Proxies: pp.Proxies(),
TestUrl: pp.healthCheck.url,
ExpectedStatus: pp.healthCheck.expectedStatus.String(),
UpdatedAt: pp.UpdatedAt(),
SubscriptionInfo: pp.subscriptionInfo,
})
}
func (pp *proxySetProvider) Version() uint32 {
return pp.version
}
func (pp *proxySetProvider) Name() string {
return pp.Fetcher.Name()
}
func (pp *proxySetProvider) HealthCheck() {
pp.healthCheck.check()
}
func (pp *proxySetProvider) Update() error {
_, _, err := pp.Fetcher.Update()
return err
@@ -79,54 +137,12 @@ func (pp *proxySetProvider) Initial() error {
return err
}
if subscriptionInfo := cachefile.Cache().GetSubscriptionInfo(pp.Name()); subscriptionInfo != "" {
pp.SetSubscriptionInfo(subscriptionInfo)
pp.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
}
pp.closeAllConnections()
return nil
}
func (pp *proxySetProvider) Type() types.ProviderType {
return types.Proxy
}
func (pp *proxySetProvider) Proxies() []C.Proxy {
return pp.proxies
}
func (pp *proxySetProvider) 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) SetSubscriptionInfo(userInfo string) {
pp.subscriptionInfo = NewSubscriptionInfo(userInfo)
}
func (pp *proxySetProvider) SetProvider(provider types.ProxyProvider) {
if httpVehicle, ok := pp.Vehicle().(*resource.HTTPVehicle); ok {
httpVehicle.SetProvider(provider)
}
}
func (pp *proxySetProvider) closeAllConnections() {
statistic.DefaultManager.Range(func(c statistic.Tracker) bool {
for _, chain := range c.Chains() {
@@ -140,44 +156,35 @@ func (pp *proxySetProvider) closeAllConnections() {
}
func (pp *proxySetProvider) Close() error {
pp.healthCheck.close()
_ = pp.baseProvider.Close()
return pp.Fetcher.Close()
}
func NewProxySetProvider(name string, interval time.Duration, filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema, vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
filterRegs = append(filterRegs, filterReg)
}
func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
if hc.auto() {
go hc.process()
}
pd := &proxySetProvider{
proxies: []C.Proxy{},
healthCheck: hc,
baseProvider: baseProvider{
name: name,
proxies: []C.Proxy{},
healthCheck: hc,
},
}
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, proxiesOnUpdate(pd))
pd.Fetcher = fetcher
wrapper := &ProxySetProvider{pd}
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
httpVehicle.SetProvider(wrapper)
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
}
@@ -187,8 +194,73 @@ func (pp *ProxySetProvider) Close() error {
return pp.proxySetProvider.Close()
}
func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) {
pp.proxySetProvider.SetProvider(provider)
// 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
@@ -197,36 +269,20 @@ type CompatibleProvider struct {
}
type compatibleProvider struct {
name string
healthCheck *HealthCheck
subscriptionInfo *SubscriptionInfo
proxies []C.Proxy
version uint32
baseProvider
}
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]any{
"name": cp.Name(),
"type": cp.Type().String(),
"vehicleType": cp.VehicleType().String(),
"proxies": cp.Proxies(),
"testUrl": cp.healthCheck.url,
"expectedStatus": cp.healthCheck.expectedStatus.String(),
return json.Marshal(providerForApi{
Name: cp.Name(),
Type: cp.Type().String(),
VehicleType: cp.VehicleType().String(),
Proxies: cp.Proxies(),
TestUrl: cp.healthCheck.url,
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
})
}
func (cp *compatibleProvider) Version() uint32 {
return cp.version
}
func (cp *compatibleProvider) Name() string {
return cp.name
}
func (cp *compatibleProvider) HealthCheck() {
cp.healthCheck.check()
}
func (cp *compatibleProvider) Update() error {
return nil
}
@@ -242,39 +298,6 @@ func (cp *compatibleProvider) VehicleType() types.VehicleType {
return types.Compatible
}
func (cp *compatibleProvider) Type() types.ProviderType {
return types.Proxy
}
func (cp *compatibleProvider) Proxies() []C.Proxy {
return cp.proxies
}
func (cp *compatibleProvider) 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 (cp *compatibleProvider) SetSubscriptionInfo(userInfo string) {
cp.subscriptionInfo = NewSubscriptionInfo(userInfo)
}
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")
@@ -285,9 +308,11 @@ func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*Co
}
pd := &compatibleProvider{
name: name,
proxies: proxies,
healthCheck: hc,
baseProvider: baseProvider{
name: name,
proxies: proxies,
healthCheck: hc,
},
}
wrapper := &CompatibleProvider{pd}
@@ -307,7 +332,25 @@ func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
}
}
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
func NewProxiesParser(filter string, excludeFilter string, excludeType string, dialerProxy string, override OverrideSchema) (resource.Parser[[]C.Proxy], error) {
excludeFilterReg, err := regexp2.Compile(excludeFilter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid excludeFilter regex: %w", err)
}
var excludeTypeArray []string
if excludeType != "" {
excludeTypeArray = strings.Split(excludeType, "|")
}
var filterRegs []*regexp2.Regexp
for _, filter := range strings.Split(filter, "`") {
filterReg, err := regexp2.Compile(filter, regexp2.None)
if err != nil {
return nil, fmt.Errorf("invalid filter regex: %w", err)
}
filterRegs = append(filterRegs, filterReg)
}
return func(buf []byte) ([]C.Proxy, error) {
schema := &ProxySchema{}
@@ -422,5 +465,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
}
return proxies, nil
}
}, nil
}

View File

@@ -42,7 +42,6 @@ func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
si.Expire = intValue
}
}
return si
}

View 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
}

View File

@@ -1,9 +1,5 @@
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
package dialer
package sockopt
import (
"net"
)
func addrReuseToListenConfig(*net.ListenConfig) {}
func reuseControl(fd uintptr) error { return nil }

View 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
}

View 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)
}

View File

@@ -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
}

View File

@@ -1,11 +0,0 @@
//go:build !linux
package sockopt
import (
"net"
)
func UDPReuseaddr(c *net.UDPConn) (err error) {
return
}

View File

@@ -86,7 +86,27 @@ func (d *Decoder) Decode(src map[string]any, dst any) error {
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 {
if isNil(data) {
// If the data is nil, then we don't set anything
// Maybe we should set to zero value?
return nil
}
if !reflect.ValueOf(data).IsValid() {
// If the input value is invalid, then we just set the value
// to be the zero value.
val.Set(reflect.Zero(val.Type()))
return nil
}
for {
kind := val.Kind()
if kind == reflect.Pointer && val.IsNil() {

View File

@@ -267,3 +267,21 @@ func TestStructure_TextUnmarshaller(t *testing.T) {
err = decoder.Decode(rawMap, s)
assert.NotNilf(t, err, "should throw error: %#v", s)
}
func TestStructure_Null(t *testing.T) {
rawMap := map[string]any{
"opt": map[string]any{
"bar": nil,
},
}
s := struct {
Opt struct {
Bar string `test:"bar,optional"`
} `test:"opt,optional"`
}{}
err := decoder.Decode(rawMap, &s)
assert.Nil(t, err)
assert.Equal(t, s.Opt.Bar, "")
}

View File

@@ -139,10 +139,13 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
}
for _, r := range ranges {
for i := r.Start(); i <= r.End(); i++ {
for i := r.Start(); i <= r.End() && i >= r.Start(); i++ {
if !f(i) {
return
}
if i+1 < i { // integer overflow
break
}
}
}
}

View File

@@ -17,7 +17,6 @@ import (
C "github.com/metacubex/mihomo/constant"
)
var trustCerts []*x509.Certificate
var globalCertPool *x509.CertPool
var mutex sync.RWMutex
var errNotMatch = errors.New("certificate fingerprints do not match")
@@ -30,11 +29,19 @@ var DisableSystemCa, _ = strconv.ParseBool(os.Getenv("DISABLE_SYSTEM_CA"))
func AddCertificate(certificate string) error {
mutex.Lock()
defer mutex.Unlock()
if certificate == "" {
return fmt.Errorf("certificate is empty")
}
if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
trustCerts = append(trustCerts, cert)
if globalCertPool == nil {
initializeCertPool()
}
if globalCertPool.AppendCertsFromPEM([]byte(certificate)) {
return nil
} else if cert, err := x509.ParseCertificate([]byte(certificate)); err == nil {
globalCertPool.AddCert(cert)
return nil
} else {
return fmt.Errorf("add certificate failed")
@@ -51,9 +58,6 @@ func initializeCertPool() {
globalCertPool = x509.NewCertPool()
}
}
for _, cert := range trustCerts {
globalCertPool.AddCert(cert)
}
if !DisableEmbedCa {
globalCertPool.AppendCertsFromPEM(_CaCertificates)
}
@@ -62,7 +66,6 @@ func initializeCertPool() {
func ResetCertificate() {
mutex.Lock()
defer mutex.Unlock()
trustCerts = nil
initializeCertPool()
}

View File

@@ -5,13 +5,11 @@ import (
"net"
"syscall"
"golang.org/x/sys/windows"
"github.com/metacubex/mihomo/common/sockopt"
)
func addrReuseToListenConfig(lc *net.ListenConfig) {
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
return c.Control(func(fd uintptr) {
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
})
return sockopt.RawConnReuseaddr(c)
})
}

View File

@@ -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)
})
})
}

View 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())
}
}

View 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[:])
}

View File

@@ -15,6 +15,7 @@ type Interface struct {
Name string
Addresses []netip.Prefix
HardwareAddr net.HardwareAddr
Flags net.Flags
}
var (
@@ -66,6 +67,7 @@ func Interfaces() (map[string]*Interface, error) {
Name: iface.Name,
Addresses: ipNets,
HardwareAddr: iface.HardwareAddr,
Flags: iface.Flags,
}
}

View File

@@ -35,7 +35,7 @@ func KeepAliveInterval() time.Duration {
func SetDisableKeepAlive(disable bool) {
if runtime.GOOS == "android" {
setDisableKeepAlive(false)
setDisableKeepAlive(true)
} else {
setDisableKeepAlive(disable)
}

View File

@@ -4,14 +4,25 @@ import (
"errors"
"fmt"
"net/netip"
"os"
"strconv"
"github.com/metacubex/mihomo/common/callback"
"github.com/metacubex/mihomo/component/iface"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/constant/features"
"github.com/puzpuzpuz/xsync/v3"
)
var disableLoopBackDetector, _ = strconv.ParseBool(os.Getenv("DISABLE_LOOPBACK_DETECTOR"))
func init() {
if features.CMFA {
disableLoopBackDetector = true
}
}
var ErrReject = errors.New("reject loopback connection")
type Detector struct {
@@ -20,6 +31,9 @@ type Detector struct {
}
func NewDetector() *Detector {
if disableLoopBackDetector {
return nil
}
return &Detector{
connMap: xsync.NewMapOf[netip.AddrPort, struct{}](),
packetConnMap: xsync.NewMapOf[uint16, struct{}](),
@@ -27,6 +41,9 @@ func NewDetector() *Detector {
}
func (l *Detector) NewConn(conn C.Conn) C.Conn {
if l == nil || l.connMap == nil {
return conn
}
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
@@ -42,6 +59,9 @@ func (l *Detector) NewConn(conn C.Conn) C.Conn {
}
func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
if l == nil || l.packetConnMap == nil {
return conn
}
metadata := C.Metadata{}
if metadata.SetRemoteAddr(conn.LocalAddr()) != nil {
return conn
@@ -58,6 +78,9 @@ func (l *Detector) NewPacketConn(conn C.PacketConn) C.PacketConn {
}
func (l *Detector) CheckConn(metadata *C.Metadata) error {
if l == nil || l.connMap == nil {
return nil
}
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil
@@ -69,6 +92,9 @@ func (l *Detector) CheckConn(metadata *C.Metadata) error {
}
func (l *Detector) CheckPacketConn(metadata *C.Metadata) error {
if l == nil || l.packetConnMap == nil {
return nil
}
connAddr := metadata.SourceAddrPort()
if !connAddr.IsValid() {
return nil

View File

@@ -5,6 +5,7 @@ import (
"net"
"strings"
"github.com/metacubex/mihomo/log"
"github.com/oschwald/maxminddb-golang"
)
@@ -23,11 +24,16 @@ type ASNReader struct {
*maxminddb.Reader
}
type ASNResult struct {
type GeoLite2 struct {
AutonomousSystemNumber uint32 `maxminddb:"autonomous_system_number"`
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 {
switch r.databaseType {
case typeMaxmind:
@@ -66,8 +72,18 @@ func (r IPReader) LookupCode(ipAddress net.IP) []string {
}
}
func (r ASNReader) LookupASN(ip net.IP) ASNResult {
var result ASNResult
r.Lookup(ip, &result)
return result
func (r ASNReader) LookupASN(ip net.IP) (string, string) {
switch r.Metadata.DatabaseType {
case "GeoLite2-ASN", "DBIP-ASN-Lite (compat=GeoLite2-ASN)":
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 "", ""
}

View File

@@ -180,7 +180,7 @@ func newSearcher(isV4, isTCP bool) *searcher {
func getTransportTable(fn uintptr, family int, class int) ([]byte, error) {
for size, buf := uint32(8), make([]byte, 8); ; {
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 {
case 0:
@@ -215,13 +215,13 @@ func getExecPathFromPID(pid uint32) (string, error) {
buf := make([]uint16, syscall.MAX_LONG_PATH)
size := uint32(len(buf))
r1, _, err := syscall.SyscallN(
queryProcName,
r1, _, err := syscall.Syscall6(
queryProcName, 4,
uintptr(h),
uintptr(0),
uintptr(unsafe.Pointer(&buf[0])),
uintptr(unsafe.Pointer(&size)),
)
0, 0)
if r1 == 0 {
return "", err
}

View File

@@ -84,12 +84,14 @@ func NewFileVehicle(path string) *FileVehicle {
}
type HTTPVehicle struct {
url string
path string
proxy string
header http.Header
timeout time.Duration
provider types.ProxyProvider
url string
path string
proxy string
header http.Header
timeout time.Duration
sizeLimit int64
inRead func(response *http.Response)
provider types.ProxyProvider
}
func (h *HTTPVehicle) Url() string {
@@ -112,8 +114,8 @@ func (h *HTTPVehicle) Write(buf []byte) error {
return safeWrite(h.path, buf)
}
func (h *HTTPVehicle) SetProvider(provider types.ProxyProvider) {
h.provider = provider
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) {
@@ -139,9 +141,8 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
}
defer resp.Body.Close()
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); h.provider != nil && subscriptionInfo != "" {
cachefile.Cache().SetSubscriptionInfo(h.provider.Name(), subscriptionInfo)
h.provider.SetSubscriptionInfo(subscriptionInfo)
if h.inRead != nil {
h.inRead(resp)
}
if resp.StatusCode < 200 || resp.StatusCode > 299 {
@@ -151,7 +152,11 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
err = errors.New(resp.Status)
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 {
return
}
@@ -166,12 +171,13 @@ func (h *HTTPVehicle) Read(ctx context.Context, oldHash utils.HashType) (buf []b
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{
url: url,
path: path,
proxy: proxy,
header: header,
timeout: timeout,
url: url,
path: path,
proxy: proxy,
header: header,
timeout: timeout,
sizeLimit: sizeLimit,
}
}

View File

@@ -48,6 +48,10 @@ func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
if metadata.DNSMode == C.DNSMapping && sd.forceDnsMapping {
return true
}
return sd.forceSniff(metadata)
}
func (sd *Dispatcher) forceSniff(metadata *C.Metadata) bool {
for _, matcher := range sd.forceDomain {
if matcher.MatchDomain(metadata.Host) {
return true
@@ -98,16 +102,21 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
if !inWhitelist {
return false
}
forceSniffer := sd.forceSniff(metadata)
dst := metadata.AddrPort()
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
return false
if !forceSniffer {
if count, ok := sd.skipList.Get(dst); ok && count > 5 {
log.Debugln("[Sniffer] Skip sniffing[%s] due to multiple failures", dst)
return false
}
}
host, err := sd.sniffDomain(conn, metadata)
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)
return false
}
@@ -145,8 +154,12 @@ func (sd *Dispatcher) Enable() bool {
}
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 {
if s.SupportNetwork() == C.TCP {
if s.SupportNetwork() == C.TCP && s.SupportPort(metadata.DstPort) {
_ = conn.SetReadDeadline(time.Now().Add(1 * time.Second))
_, err := conn.Peek(1)
_ = conn.SetReadDeadline(time.Time{})
@@ -154,7 +167,7 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
_, ok := err.(*net.OpError)
if ok {
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()
}
@@ -164,22 +177,36 @@ func (sd *Dispatcher) sniffDomain(conn *N.BufferedConn, metadata *C.Metadata) (s
bufferedLen := conn.Buffered()
bytes, err := conn.Peek(bufferedLen)
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
}
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 {
//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
}
_, err = netip.ParseAddr(host)
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
}
//log.Debugln("[Sniffer] [%s] [%s] Sniffed [%s]", metadata.DstIP, s.Protocol(), host)
return host, nil
}
}

View File

@@ -3,6 +3,7 @@ package sniffer
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/metacubex/mihomo/common/utils"
@@ -15,6 +16,19 @@ var (
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)
type TLSSniffer struct {
@@ -160,7 +174,10 @@ func SniffTLS(b []byte) (*string, error) {
}
headerLen := int(binary.BigEndian.Uint16(b[3:5]))
if 5+headerLen > len(b) {
return nil, ErrNoClue
return nil, &errNeedAtLeastData{
length: 5 + headerLen,
err: ErrNoClue,
}
}
domain, err := ReadClientHello(b[5 : 5+headerLen])

View File

@@ -45,7 +45,7 @@ func SetGeoUpdateInterval(newGeoUpdateInterval int) {
}
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
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
@@ -76,7 +76,7 @@ func UpdateMMDB() (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
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
@@ -109,7 +109,7 @@ func UpdateASN() (err error) {
func UpdateGeoIp() (err error) {
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
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)
@@ -139,7 +139,7 @@ func UpdateGeoIp() (err error) {
func UpdateGeoSite() (err error) {
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
if buf, err := os.ReadFile(vehicle.Path()); err == nil {
oldHash = utils.MakeHash(buf)

View File

@@ -1,7 +1,9 @@
package updater
import (
"archive/tar"
"archive/zip"
"compress/gzip"
"fmt"
"io"
"os"
@@ -22,6 +24,14 @@ type UIUpdater struct {
mutex sync.Mutex
}
type compressionType int
const (
typeUnknown compressionType = iota
typeZip
typeTarGzip
)
var DefaultUiUpdater = &UIUpdater{}
func NewUiUpdater(externalUI, externalUIURL, externalUIName string) *UIUpdater {
@@ -70,6 +80,24 @@ func (u *UIUpdater) DownloadUI() error {
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 {
@@ -78,12 +106,23 @@ func (u *UIUpdater) downloadUI() error {
data, err := downloadForBytes(u.externalUIURL)
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 {
return fmt.Errorf("can't save zip file: %w", err)
return fmt.Errorf("can't save compressed file: %w", err)
}
defer os.Remove(saved)
@@ -94,12 +133,12 @@ func (u *UIUpdater) downloadUI() error {
}
}
unzipFolder, err := unzip(saved, C.Path.HomeDir())
extractedFolder, err := extract(saved, C.Path.HomeDir())
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, u.externalUIPath)
err = os.Rename(extractedFolder, u.externalUIPath)
if err != nil {
return fmt.Errorf("rename UI folder failed: %w", err)
}
@@ -122,9 +161,66 @@ func unzip(src, dest string) (string, error) {
return "", err
}
defer r.Close()
var extractedFolder string
// check whether or not only exists singleRoot dir
rootDir := ""
isSingleRoot := true
rootItemCount := 0
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)) {
return "", fmt.Errorf("invalid file path: %s", fpath)
}
@@ -149,13 +245,158 @@ func unzip(src, dest string) (string, error) {
if err != nil {
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
}
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 {
if _, err := os.Stat(root); os.IsNotExist(err) {
return nil

View File

@@ -42,6 +42,8 @@ const (
WireGuard
Tuic
Ssh
Mieru
AnyTLS
)
const (
@@ -99,13 +101,24 @@ type Dialer interface {
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 {
Name() string
Type() AdapterType
Addr() string
SupportUDP() bool
SupportXUDP() bool
SupportTFO() bool
// ProxyInfo contains some extra information maybe useful for MarshalJSON
ProxyInfo() ProxyInfo
MarshalJSON() ([]byte, error)
// Deprecated: use DialContextWithDialer and ListenPacketWithDialer instead.
@@ -215,7 +228,10 @@ func (at AdapterType) String() string {
return "Tuic"
case Ssh:
return "Ssh"
case Mieru:
return "Mieru"
case AnyTLS:
return "AnyTLS"
case Relay:
return "Relay"
case Selector:

View File

@@ -25,12 +25,15 @@ const (
SOCKS5
SHADOWSOCKS
VMESS
VLESS
REDIR
TPROXY
TROJAN
TUNNEL
TUN
TUIC
HYSTERIA2
ANYTLS
INNER
)
@@ -69,10 +72,14 @@ func (t Type) String() string {
return "ShadowSocks"
case VMESS:
return "Vmess"
case VLESS:
return "Vless"
case REDIR:
return "Redir"
case TPROXY:
return "TProxy"
case TROJAN:
return "Trojan"
case TUNNEL:
return "Tunnel"
case TUN:
@@ -81,6 +88,8 @@ func (t Type) String() string {
return "Tuic"
case HYSTERIA2:
return "Hysteria2"
case ANYTLS:
return "AnyTLS"
case INNER:
return "Inner"
default:
@@ -103,10 +112,14 @@ func ParseType(t string) (*Type, error) {
res = SHADOWSOCKS
case "VMESS":
res = VMESS
case "VLESS":
res = VLESS
case "REDIR":
res = REDIR
case "TPROXY":
res = TPROXY
case "TROJAN":
res = TROJAN
case "TUNNEL":
res = TUNNEL
case "TUN":
@@ -115,6 +128,8 @@ func ParseType(t string) (*Type, error) {
res = TUIC
case "HYSTERIA2":
res = HYSTERIA2
case "ANYTLS":
res = ANYTLS
case "INNER":
res = INNER
default:

View File

@@ -13,6 +13,7 @@ const (
File VehicleType = iota
HTTP
Compatible
Inline
)
// VehicleType defined
@@ -26,6 +27,8 @@ func (v VehicleType) String() string {
return "HTTP"
case Compatible:
return "Compatible"
case Inline:
return "Inline"
default:
return "Unknown"
}
@@ -81,7 +84,6 @@ type ProxyProvider interface {
Version() uint32
RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint)
HealthCheckURL() string
SetSubscriptionInfo(userInfo string)
}
// RuleProvider interface

View File

@@ -37,13 +37,14 @@ const (
transportDefaultIdleConnTimeout = 5 * time.Minute
// dohMaxConnsPerHost controls the maximum number of connections for
// each host.
dohMaxConnsPerHost = 1
// each host. Note, that setting it to 1 may cause issues with Go's http
// implementation, see https://github.com/AdguardTeam/dnsproxy/issues/278.
dohMaxConnsPerHost = 2
dialTimeout = 10 * time.Second
// dohMaxIdleConns controls the maximum number of connections being idle
// at the same time.
dohMaxIdleConns = 1
dohMaxIdleConns = 2
maxElapsedTime = time.Second * 30
)

View File

@@ -5,6 +5,7 @@ import (
"errors"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/sockopt"
"github.com/metacubex/mihomo/context"
"github.com/metacubex/mihomo/log"
@@ -20,8 +21,9 @@ var (
)
type Server struct {
*D.Server
handler handler
handler handler
tcpServer *D.Server
udpServer *D.Server
}
// ServeDNS implement D.Handler ServeDNS
@@ -55,12 +57,19 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
return
}
if server.Server != nil {
server.Shutdown()
server = &Server{}
address = ""
if server.tcpServer != nil {
_ = server.tcpServer.Shutdown()
server.tcpServer = nil
}
if server.udpServer != nil {
_ = server.udpServer.Shutdown()
server.udpServer = nil
}
server.handler = nil
address = ""
if addr == "" {
return
}
@@ -77,31 +86,36 @@ func ReCreateServer(addr string, resolver *Resolver, mapper *ResolverEnhancer) {
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
handler := NewHandler(resolver, mapper)
server = &Server{handler: handler}
server.Server = &D.Server{Addr: addr, PacketConn: p, Handler: server}
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())
}

View File

@@ -61,7 +61,7 @@ external-controller-tls: 0.0.0.0:9443 # RESTful API HTTPS 监听地址,需要
# RESTful API CORS标头配置
external-controller-cors:
allow-origins:
- *
- "*"
allow-private-network: true
# RESTful API Unix socket 监听地址( windows版本大于17063也可以使用即大于等于1803/RS4版本即可使用
@@ -78,6 +78,7 @@ external-controller-pipe: \\.\pipe\mihomo
# 配置 WEB UI 目录,使用 http://{{external-controller}}/ui 访问
external-ui: /path/to/ui/folder/
external-ui-name: xd
# 目前支持下载zip,tgz格式的压缩包
external-ui-url: "https://github.com/MetaCubeX/metacubexd/archive/refs/heads/gh-pages.zip"
# 在RESTful API端口上开启DOH服务器
@@ -747,6 +748,11 @@ proxies: # socks5
# - h3
# ca: "./my.ca"
# 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
- name: "wg"
@@ -846,6 +852,35 @@ proxies: # socks5
password: password
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 模块,所有请求均在内部处理
- name: "dns-out"
type: dns
@@ -930,6 +965,7 @@ proxy-providers:
interval: 3600
path: ./provider1.yaml # 默认只允许存储在 mihomo 的 Home Dir如果想存储到任意位置添加环境变量 SKIP_SAFE_PATH_CHECK=1
proxy: DIRECT
# size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小
header:
User-Agent:
- "Clash/v1.18.0"
@@ -962,6 +998,17 @@ proxy-providers:
# - pattern: "IPLC-(.*?)倍"
# 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:
type: file
path: /test.yaml
@@ -977,6 +1024,7 @@ rule-providers:
type: http # http 的 path 可空置,默认储存路径为 homedir 的 rules 文件夹,文件名为 url 的 md5
url: "url"
proxy: DIRECT
# size-limit: 10240 # 限制下载文件最大为10kb默认为0即不限制文件大小
rule2:
behavior: classical
interval: 259200
@@ -1000,6 +1048,14 @@ rule-providers:
format: mrs
behavior: domain
path: /path/to/save/file.mrs
rule4:
type: inline
behavior: domain # classical / ipcidr
payload:
- '.blogger.com'
- '*.*.microsoft.com'
- 'books.itunes.apple.com'
rules:
- RULE-SET,rule1,REJECT
- IP-ASN,1,PROXY
@@ -1040,7 +1096,7 @@ sub-rules:
listeners:
- name: socks5-in-1
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
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
@@ -1048,20 +1104,26 @@ listeners:
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: http-in-1
type: http
port: 10809
port: 10809 # 支持使用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: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: mixed-in-1
type: mixed # HTTP(S) 和 SOCKS 代理混合
port: 10810
port: 10810 # 支持使用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 名称必须合法,否则会出错)
@@ -1069,17 +1131,20 @@ listeners:
# users: # 如果不填写users项则遵从全局authentication设置如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
# - username: aaa
# password: aaa
# 下面两项如果填写则开启 tls需要同时填写
# certificate: ./server.crt
# private-key: ./server.key
- name: reidr-in-1
type: redir
port: 10811
port: 10811 # 支持使用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 名称必须合法,否则会出错)
- name: tproxy-in-1
type: tproxy
port: 10812
port: 10812 # 支持使用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 名称必须合法,否则会出错)
@@ -1087,7 +1152,7 @@ listeners:
- name: shadowsocks-in-1
type: shadowsocks
port: 10813
port: 10813 # 支持使用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 名称必须合法,否则会出错)
@@ -1096,7 +1161,7 @@ listeners:
- name: vmess-in-1
type: vmess
port: 10814
port: 10814 # 支持使用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 名称必须合法,否则会出错)
@@ -1105,13 +1170,22 @@ listeners:
uuid: 9d0cb9d0-964f-4ef6-897d-6c6b3ccf9e68
alterId: 1
# 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
- name: tuic-in-1
type: tuic
port: 10815
port: 10815 # 支持使用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 名称必须合法,否则会出错)
@@ -1131,13 +1205,78 @@ listeners:
- name: tunnel-in-1
type: tunnel
port: 10816
port: 10816 # 支持使用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 名称必须合法,否则会出错)
network: [tcp, udp]
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
type: tun
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
@@ -1200,4 +1339,4 @@ listeners:
# authentication-timeout: 1000
# alpn:
# - h3
# max-udp-relay-packet-size: 1500
# max-udp-relay-packet-size: 1500

73
go.mod
View File

@@ -6,56 +6,59 @@ require (
github.com/3andne/restls-client-go v0.1.6
github.com/bahlo/generic-list-go v0.2.0
github.com/coreos/go-iptables v0.8.0
github.com/dlclark/regexp2 v1.11.4
github.com/go-chi/chi/v5 v5.1.0
github.com/dlclark/regexp2 v1.11.5
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/gobwas/ws v1.4.0
github.com/gofrs/uuid/v5 v5.3.0
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475
github.com/klauspost/compress v1.17.9
github.com/klauspost/cpuid/v2 v2.2.8
github.com/gofrs/uuid/v5 v5.3.1
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905
github.com/klauspost/compress v1.17.9 // lastest version compatible with golang1.20
github.com/klauspost/cpuid/v2 v2.2.9
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
github.com/mdlayher/netlink v1.7.2
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
github.com/metacubex/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/quic-go v0.48.1-0.20241021013658-51ca987e0174
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996
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-shadowsocks2 v0.2.2
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa
github.com/metacubex/sing-tun v0.4.5
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/metacubex/utls v1.6.6
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/openacid/low v0.1.21
github.com/oschwald/maxminddb-golang v1.12.0
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/oschwald/maxminddb-golang v1.12.0 // lastest version compatible with golang1.20
github.com/puzpuzpuz/xsync/v3 v3.5.0
github.com/sagernet/cors v1.2.1
github.com/sagernet/fswatch v0.1.1
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/sagernet/sing v0.5.0-rc.4
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6
github.com/sagernet/sing-shadowtls v0.1.4
github.com/samber/lo v1.47.0
github.com/shirou/gopsutil/v3 v3.24.5
github.com/sagernet/sing v0.5.2
github.com/sagernet/sing-mux v0.2.1
github.com/sagernet/sing-shadowtls v0.1.5
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/samber/lo v1.49.1
github.com/shirou/gopsutil/v4 v4.25.1
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/wk8/go-ordered-map/v2 v2.1.8
gitlab.com/go-extension/aes-ccm v0.0.0-20230221065045-e58665ef23c7
go.uber.org/automaxprocs v1.6.0
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba
golang.org/x/crypto v0.28.0
golang.org/x/crypto v0.33.0
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
golang.org/x/net v0.30.0
golang.org/x/sys v0.26.0
google.golang.org/protobuf v1.34.2
golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
gopkg.in/yaml.v3 v3.0.1
lukechampine.com/blake3 v1.3.0
)
@@ -68,6 +71,7 @@ require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 // indirect
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 // 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/gobwas/httphead v0.1.0 // 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/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/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/metacubex/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/onsi/ginkgo/v2 v2.9.5 // 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/qtls-go1-20 v0.4.1 // 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/rabaead v0.0.0-20220730151906-ab6e06b96e8c // 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
go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.20.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.24.0 // indirect
)
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a

133
go.sum
View File

@@ -21,12 +21,15 @@ github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vc
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/coreos/go-iptables v0.8.0 h1:MPc2P89IhuVpLI7ETL/2tx3XZ61VeICZjYqDEgNsPRc=
github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/ebitengine/purego v0.8.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/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
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/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk=
github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI=
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
@@ -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/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
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.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
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.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
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.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
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/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
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.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
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/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475 h1:hxST5pwMBEOWmxpkX20w9oZG+hXdhKmAIPQ3NGGAxas=
github.com/insomniacslk/dhcp v0.0.0-20240829085014-a3a4c1f04475/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/insomniacslk/dhcp v0.0.0-20250109001534-8abf58130905 h1:q3OEI9RaN/wwcx+qgGo6ZaoJkCiDYe/gjDLfq7lQQF4=
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/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/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0VBNqY/88RNnhSGYkrHaO0mmFGbVsc=
@@ -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/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
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/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec h1:HxreOiFTUrJXJautEo8rnE1uKTVGY8wtZepY1Tii/Nc=
github.com/metacubex/gvisor v0.0.0-20240320004321-933faba989ec/go.mod h1:8BVmQ+3cxjqzWElafm24rb2Ae4jRI6vAXNXWqWjfrXw=
github.com/metacubex/quic-go v0.48.1-0.20241021013658-51ca987e0174 h1:GvigRPEU+cbnzdLWne47cxLrc28Abohl3ECtVdnrbq0=
github.com/metacubex/quic-go v0.48.1-0.20241021013658-51ca987e0174/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA=
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a h1:cZ6oNVrsmsi3SNlnSnRio4zOgtQq+/XidwsaNgKICcg=
github.com/metacubex/gvisor v0.0.0-20241126021258-5b028898cc5a/go.mod h1:xBw/SYJPgUMPQ1tklV/brGn2nxhfr3BnvBzNlyi4Nic=
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.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/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a h1:JuR0/7RDxQtlZp/GOzrdqNq04HplTxavPRHrek8ouJk=
github.com/metacubex/sing v0.0.0-20241021005542-18b67490300a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4 h1:HobpULaPK6OoxrHMmgcwLkwwIduXVmwdcznwUfH1GQM=
github.com/metacubex/sing-quic v0.0.0-20240827003841-cd97758ed8b4/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
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/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/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c h1:qfUZ8xBrViOCZamvcC8CyV7Ii8sAUrn7RqZxFGn56tQ=
github.com/metacubex/sing-tun v0.2.7-0.20241021011113-857bcd6ee47c/go.mod h1:lCrP0AW7ieKnXG1JEeZLW+9h99QzjuOX0MfCQfz6TgE=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3 h1:xg71VmzLS6ByAzi/57phwDvjE+dLLs+ozH00k4DnOns=
github.com/metacubex/sing-wireguard v0.0.0-20240924052438-b0976fc59ea3/go.mod h1:6nitcmzPDL3MXnLdhu6Hm126Zk4S1fBbX3P7jxUxSFw=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
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-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
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/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/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs=
github.com/mroth/weightedrand/v2 v2.1.0 h1:o1ascnB1CIVzsqlfArQQjeMy1U0NcIbBO5rfd5E/OeU=
github.com/mroth/weightedrand/v2 v2.1.0/go.mod h1:f2faGsfOGOwc1p94wzHKKZyTpcJUW7OJ/9U4yfiNAOU=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 h1:1102pQc2SEPp5+xrS26wEaeb26sZy6k9/ZXlZN+eXE4=
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7/go.mod h1:UqoUn6cHESlliMhOnKLWr+CBH+e3bazUPvFj1XZwAjs=
github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
@@ -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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
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.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/puzpuzpuz/xsync/v3 v3.5.0 h1:i+cMcpEDY1BkNm7lPDkCtE4oElsYLn+EKF8kAu2vXT4=
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/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
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/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/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6 h1:5bCAkvDDzSMITiHFjolBwpdqYsvycdTu71FsMEFXQ14=
github.com/sagernet/sing-mux v0.2.1-0.20240124034317-9bfb33698bb6/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ=
github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k=
github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4=
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
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/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
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/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
github.com/samber/lo v1.49.1/go.mod h1:dO6KHFzUKXgP8LDhU0oI8d2hekjXnGOu0DB8Jecxd6o=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/sina-ghaderi/poly1305 v0.0.0-20220724002748-c5926b03988b h1:rXHg9GrUEtWZhEkrykicdND3VPjlVbYiLdX9J7gimS8=
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=
@@ -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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
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/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
@@ -230,8 +232,8 @@ 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=
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk=
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=
@@ -240,11 +242,11 @@ 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/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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
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.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
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-20190412213103-97732733099d/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-20220715151400-c0bba94af5f8/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.8.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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
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.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
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/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -127,10 +127,10 @@ func router(isDebug bool, secret string, dohServer string, cors Cors) *chi.Mux {
r.Mount("/providers/rules", ruleProviderRouter())
r.Mount("/cache", cacheRouter())
r.Mount("/dns", dnsRouter())
if !embedMode { // disallow restart and upgrade in embed mode
if !embedMode { // disallow restart in embed mode
r.Mount("/restart", restartRouter())
r.Mount("/upgrade", upgradeRouter())
}
r.Mount("/upgrade", upgradeRouter())
addExternalRouters(r)
})

View File

@@ -14,9 +14,11 @@ import (
func upgradeRouter() http.Handler {
r := chi.NewRouter()
r.Post("/", upgradeCore)
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
}

184
listener/anytls/server.go Normal file
View 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
View 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
View 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
}

View File

@@ -23,6 +23,12 @@ type Hysteria2Server struct {
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
UdpMTU int `yaml:"udp-mtu" json:"udp-mtu,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 {

38
listener/config/trojan.go Normal file
View 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
View 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)
}

View File

@@ -1,9 +1,10 @@
package config
import (
"github.com/metacubex/mihomo/listener/sing"
"encoding/json"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/sing"
)
type VmessUser struct {
@@ -13,13 +14,15 @@ type VmessUser struct {
}
type VmessServer struct {
Enable bool
Listen string
Users []VmessUser
WsPath string
Certificate string
PrivateKey string
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
Enable bool
Listen string
Users []VmessUser
WsPath string
GrpcServiceName string
Certificate string
PrivateKey string
RealityConfig reality.Config
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
}
func (t VmessServer) String() string {

View File

@@ -1,12 +1,16 @@
package http
import (
"crypto/tls"
"errors"
"net"
"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"
authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/reality"
)
type Listener struct {
@@ -32,7 +36,7 @@ func (l *Listener) Close() 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
@@ -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) {
store := authStore.Default
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
if len(additions) == 0 {
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 {
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{
listener: l,
addr: addr,
addr: config.Listen,
}
go func() {
for {
conn, err := hl.listener.Accept()
@@ -74,7 +105,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
continue
}
store := store
store := config.AuthStore
if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(conn.RemoteAddr()) {
_ = conn.Close()

View 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)

View File

@@ -5,8 +5,10 @@ import (
"net"
"net/netip"
"strconv"
"strings"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/common/utils"
C "github.com/metacubex/mihomo/constant"
)
@@ -15,7 +17,7 @@ type Base struct {
name string
specialRules string
listenAddr netip.Addr
port int
ports utils.IntRanges[uint16]
}
func NewBase(options *BaseOption) (*Base, error) {
@@ -26,11 +28,15 @@ func NewBase(options *BaseOption) (*Base, error) {
if err != nil {
return nil, err
}
ports, err := utils.NewUnsignedRanges[uint16](options.Port)
if err != nil {
return nil, err
}
return &Base{
name: options.Name(),
listenAddr: addr,
specialRules: options.SpecialRules,
port: options.Port,
ports: ports,
config: options,
}, nil
}
@@ -57,7 +63,15 @@ func (b *Base) Name() string {
// RawAddress implements constant.InboundListener
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
@@ -74,7 +88,7 @@ var _ C.InboundListener = (*Base)(nil)
type BaseOption struct {
NameStr string `inbound:"name"`
Listen string `inbound:"listen,omitempty"`
Port int `inbound:"port,omitempty"`
Port string `inbound:"port,omitempty"`
SpecialRules string `inbound:"rule,omitempty"`
SpecialProxy string `inbound:"proxy,omitempty"`
}

View File

@@ -1,14 +1,22 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/log"
)
type HTTPOption struct {
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 {
@@ -18,7 +26,7 @@ func (o HTTPOption) Equal(config C.InboundConfig) bool {
type HTTP struct {
*Base
config *HTTPOption
l *http.Listener
l []*http.Listener
}
func NewHTTP(options *HTTPOption) (*HTTP, error) {
@@ -39,15 +47,32 @@ func (h *HTTP) Config() C.InboundConfig {
// Address implements constant.InboundListener
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
func (h *HTTP) Listen(tunnel C.Tunnel) error {
var err error
h.l, err = http.NewWithAuthenticator(h.RawAddress(), tunnel, h.config.Users.GetAuthStore(), h.Additions()...)
if err != nil {
return err
for _, addr := range strings.Split(h.RawAddress(), ",") {
l, err := http.NewWithConfig(
LC.AuthServer{
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())
return nil
@@ -55,8 +80,15 @@ func (h *HTTP) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (h *HTTP) Close() error {
if h.l != nil {
return h.l.Close()
var errs []error
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
}

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_hysteria2"
@@ -23,6 +25,12 @@ type Hysteria2Option struct {
CWND int `inbound:"cwnd,omitempty"`
UdpMTU int `inbound:"udp-mtu,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 {
@@ -61,6 +69,11 @@ func NewHysteria2(options *Hysteria2Option) (*Hysteria2, error) {
CWND: options.CWND,
UdpMTU: options.UdpMTU,
MuxOption: options.MuxOption.Build(),
// quic-go special config
InitialStreamReceiveWindow: options.InitialStreamReceiveWindow,
MaxStreamReceiveWindow: options.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: options.InitialConnectionReceiveWindow,
MaxConnectionReceiveWindow: options.MaxConnectionReceiveWindow,
},
}, nil
}
@@ -72,12 +85,13 @@ func (t *Hysteria2) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *Hysteria2) Address() string {
var addrList []string
if t.l != nil {
for _, addr := range t.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -1,19 +1,24 @@
package inbound
import (
"errors"
"fmt"
"strings"
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/socks"
"github.com/metacubex/mihomo/log"
)
type MixedOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Users AuthUsers `inbound:"users,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 {
@@ -23,8 +28,8 @@ func (o MixedOption) Equal(config C.InboundConfig) bool {
type Mixed struct {
*Base
config *MixedOption
l *mixed.Listener
lUDP *socks.UDPListener
l []*mixed.Listener
lUDP []*socks.UDPListener
udp bool
}
@@ -47,21 +52,39 @@ func (m *Mixed) Config() C.InboundConfig {
// Address implements constant.InboundListener
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
func (m *Mixed) Listen(tunnel C.Tunnel) error {
var err error
m.l, err = mixed.NewWithAuthenticator(m.RawAddress(), tunnel, m.config.Users.GetAuthStore(), m.Additions()...)
if err != nil {
return err
}
if m.udp {
m.lUDP, err = socks.NewUDP(m.RawAddress(), tunnel, m.Additions()...)
for _, addr := range strings.Split(m.RawAddress(), ",") {
l, err := mixed.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: m.config.Users.GetAuthStore(),
Certificate: m.config.Certificate,
PrivateKey: m.config.PrivateKey,
RealityConfig: m.config.RealityConfig.Build(),
},
tunnel,
m.Additions()...,
)
if err != nil {
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())
return nil
@@ -69,22 +92,23 @@ func (m *Mixed) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (m *Mixed) Close() error {
var err error
if m.l != nil {
if tcpErr := m.l.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range m.l {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if m.udp && m.lUDP != nil {
if udpErr := m.lUDP.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range m.lUDP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
var _ C.InboundListener = (*Mixed)(nil)

View 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,
}
}

View File

@@ -1,6 +1,10 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/redir"
"github.com/metacubex/mihomo/log"
@@ -17,7 +21,7 @@ func (o RedirOption) Equal(config C.InboundConfig) bool {
type Redir struct {
*Base
config *RedirOption
l *redir.Listener
l []*redir.Listener
}
func NewRedir(options *RedirOption) (*Redir, error) {
@@ -38,15 +42,21 @@ func (r *Redir) Config() C.InboundConfig {
// Address implements constant.InboundListener
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
func (r *Redir) Listen(tunnel C.Tunnel) error {
var err error
r.l, err = redir.New(r.RawAddress(), tunnel, r.Additions()...)
if err != nil {
return err
for _, addr := range strings.Split(r.RawAddress(), ",") {
l, err := redir.New(addr, tunnel, r.Additions()...)
if err != nil {
return err
}
r.l = append(r.l, l)
}
log.Infoln("Redir[%s] proxy listening at: %s", r.Name(), r.Address())
return nil
@@ -54,8 +64,15 @@ func (r *Redir) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (r *Redir) Close() error {
if r.l != nil {
r.l.Close()
var errs []error
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
}

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_shadowsocks"
@@ -52,12 +54,13 @@ func (s *ShadowSocks) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (s *ShadowSocks) Address() string {
var addrList []string
if s.l != nil {
for _, addr := range s.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -1,16 +1,23 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/log"
)
type SocksOption struct {
BaseOption
Users AuthUsers `inbound:"users,omitempty"`
UDP bool `inbound:"udp,omitempty"`
Users AuthUsers `inbound:"users,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 SocksOption) Equal(config C.InboundConfig) bool {
@@ -21,8 +28,8 @@ type Socks struct {
*Base
config *SocksOption
udp bool
stl *socks.Listener
sul *socks.UDPListener
stl []*socks.Listener
sul []*socks.UDPListener
}
func NewSocks(options *SocksOption) (*Socks, error) {
@@ -44,40 +51,60 @@ func (s *Socks) Config() C.InboundConfig {
// Close implements constant.InboundListener
func (s *Socks) Close() error {
var err error
if s.stl != nil {
if tcpErr := s.stl.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range s.stl {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if s.udp && s.sul != nil {
if udpErr := s.sul.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range s.sul {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// Address implements constant.InboundListener
func (s *Socks) Address() string {
return s.stl.Address()
var addrList []string
for _, l := range s.stl {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (s *Socks) Listen(tunnel C.Tunnel) error {
var err error
if s.stl, err = socks.NewWithAuthenticator(s.RawAddress(), tunnel, s.config.Users.GetAuthStore(), s.Additions()...); err != nil {
return err
}
if s.udp {
if s.sul, err = socks.NewUDP(s.RawAddress(), tunnel, s.Additions()...); err != nil {
for _, addr := range strings.Split(s.RawAddress(), ",") {
stl, err := socks.NewWithConfig(
LC.AuthServer{
Enable: true,
Listen: addr,
AuthStore: s.config.Users.GetAuthStore(),
Certificate: s.config.Certificate,
PrivateKey: s.config.PrivateKey,
RealityConfig: s.config.RealityConfig.Build(),
},
tunnel,
s.Additions()...,
)
if err != nil {
return err
}
s.stl = append(s.stl, stl)
if s.udp {
sul, err := socks.NewUDP(addr, tunnel, s.Additions()...)
if err != nil {
return err
}
s.sul = append(s.sul, sul)
}
}
log.Infoln("SOCKS[%s] proxy listening at: %s", s.Name(), s.Address())

View File

@@ -1,7 +1,9 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/tproxy"
@@ -20,8 +22,8 @@ func (o TProxyOption) Equal(config C.InboundConfig) bool {
type TProxy struct {
*Base
config *TProxyOption
lUDP *tproxy.UDPListener
lTCP *tproxy.Listener
lUDP []*tproxy.UDPListener
lTCP []*tproxy.Listener
udp bool
}
@@ -45,21 +47,28 @@ func (t *TProxy) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *TProxy) Address() string {
return t.lTCP.Address()
var addrList []string
for _, l := range t.lTCP {
addrList = append(addrList, l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (t *TProxy) Listen(tunnel C.Tunnel) error {
var err error
t.lTCP, err = tproxy.New(t.RawAddress(), tunnel, t.Additions()...)
if err != nil {
return err
}
if t.udp {
t.lUDP, err = tproxy.NewUDP(t.RawAddress(), tunnel, t.Additions()...)
for _, addr := range strings.Split(t.RawAddress(), ",") {
lTCP, err := tproxy.New(addr, tunnel, t.Additions()...)
if err != nil {
return err
}
t.lTCP = append(t.lTCP, lTCP)
if t.udp {
lUDP, err := tproxy.NewUDP(addr, tunnel, t.Additions()...)
if err != nil {
return err
}
t.lUDP = append(t.lUDP, lUDP)
}
}
log.Infoln("TProxy[%s] proxy listening at: %s", t.Name(), t.Address())
return nil
@@ -67,23 +76,21 @@ func (t *TProxy) Listen(tunnel C.Tunnel) error {
// Close implements constant.InboundListener
func (t *TProxy) Close() error {
var tcpErr error
var udpErr error
if t.lTCP != nil {
tcpErr = t.lTCP.Close()
var errs []error
for _, l := range t.lTCP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if t.lUDP != nil {
udpErr = t.lUDP.Close()
for _, l := range t.lUDP {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
if tcpErr != nil && udpErr != nil {
return fmt.Errorf("tcp close err: %s and udp close err: %s", tcpErr, udpErr)
}
if tcpErr != nil {
return tcpErr
}
if udpErr != nil {
return udpErr
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}

113
listener/inbound/trojan.go Normal file
View File

@@ -0,0 +1,113 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/trojan"
"github.com/metacubex/mihomo/log"
)
type TrojanOption struct {
BaseOption
Users []TrojanUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
SSOption TrojanSSOption `inbound:"ss-option,omitempty"`
}
type TrojanUser struct {
Username string `inbound:"username,omitempty"`
Password string `inbound:"password"`
}
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
type TrojanSSOption struct {
Enabled bool `inbound:"enabled,omitempty"`
Method string `inbound:"method,omitempty"`
Password string `inbound:"password,omitempty"`
}
func (o TrojanOption) Equal(config C.InboundConfig) bool {
return optionToString(o) == optionToString(config)
}
type Trojan struct {
*Base
config *TrojanOption
l C.MultiAddrListener
vs LC.TrojanServer
}
func NewTrojan(options *TrojanOption) (*Trojan, error) {
base, err := NewBase(&options.BaseOption)
if err != nil {
return nil, err
}
users := make([]LC.TrojanUser, len(options.Users))
for i, v := range options.Users {
users[i] = LC.TrojanUser{
Username: v.Username,
Password: v.Password,
}
}
return &Trojan{
Base: base,
config: options,
vs: LC.TrojanServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
TrojanSSOption: LC.TrojanSSOption{
Enabled: options.SSOption.Enabled,
Method: options.SSOption.Method,
Password: options.SSOption.Password,
},
},
}, nil
}
// Config implements constant.InboundListener
func (v *Trojan) Config() C.InboundConfig {
return v.config
}
// Address implements constant.InboundListener
func (v *Trojan) 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 *Trojan) Listen(tunnel C.Tunnel) error {
var err error
v.l, err = trojan.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err
}
log.Infoln("Trojan[%s] proxy listening at: %s", v.Name(), v.Address())
return nil
}
// Close implements constant.InboundListener
func (v *Trojan) Close() error {
return v.l.Close()
}
var _ C.InboundListener = (*Trojan)(nil)

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/tuic"
@@ -66,12 +68,13 @@ func (t *Tuic) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (t *Tuic) Address() string {
var addrList []string
if t.l != nil {
for _, addr := range t.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener

View File

@@ -23,18 +23,18 @@ type TunOption struct {
GSOMaxSize uint32 `inbound:"gso-max-size,omitempty"`
Inet4Address []string `inbound:"inet4-address,omitempty"`
Inet6Address []string `inbound:"inet6-address,omitempty"`
IPRoute2TableIndex int `inbound:"iproute2-table-index"`
IPRoute2RuleIndex int `inbound:"iproute2-rule-index"`
AutoRedirect bool `inbound:"auto-redirect"`
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark"`
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark"`
IPRoute2TableIndex int `inbound:"iproute2-table-index,omitempty"`
IPRoute2RuleIndex int `inbound:"iproute2-rule-index,omitempty"`
AutoRedirect bool `inbound:"auto-redirect,omitempty"`
AutoRedirectInputMark uint32 `inbound:"auto-redirect-input-mark,omitempty"`
AutoRedirectOutputMark uint32 `inbound:"auto-redirect-output-mark,omitempty"`
StrictRoute bool `inbound:"strict-route,omitempty"`
RouteAddress []string `inbound:"route-address"`
RouteAddressSet []string `inbound:"route-address-set"`
RouteExcludeAddress []string `inbound:"route-exclude-address"`
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set"`
RouteAddress []string `inbound:"route-address,omitempty"`
RouteAddressSet []string `inbound:"route-address-set,omitempty"`
RouteExcludeAddress []string `inbound:"route-exclude-address,omitempty"`
RouteExcludeAddressSet []string `inbound:"route-exclude-address-set,omitempty"`
IncludeInterface []string `inbound:"include-interface,omitempty"`
ExcludeInterface []string `inbound:"exclude-interface"`
ExcludeInterface []string `inbound:"exclude-interface,omitempty"`
IncludeUID []uint32 `inbound:"include-uid,omitempty"`
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`

View File

@@ -1,7 +1,9 @@
package inbound
import (
"errors"
"fmt"
"strings"
C "github.com/metacubex/mihomo/constant"
LT "github.com/metacubex/mihomo/listener/tunnel"
@@ -21,8 +23,8 @@ func (o TunnelOption) Equal(config C.InboundConfig) bool {
type Tunnel struct {
*Base
config *TunnelOption
ttl *LT.Listener
tul *LT.PacketConn
ttl []*LT.Listener
tul []*LT.PacketConn
}
func NewTunnel(options *TunnelOption) (*Tunnel, error) {
@@ -43,56 +45,62 @@ func (t *Tunnel) Config() C.InboundConfig {
// Close implements constant.InboundListener
func (t *Tunnel) Close() error {
var err error
if t.ttl != nil {
if tcpErr := t.ttl.Close(); tcpErr != nil {
err = tcpErr
var errs []error
for _, l := range t.ttl {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close tcp listener %s err: %w", l.Address(), err))
}
}
if t.tul != nil {
if udpErr := t.tul.Close(); udpErr != nil {
if err == nil {
err = udpErr
} else {
return fmt.Errorf("close tcp err: %s, close udp err: %s", err.Error(), udpErr.Error())
}
for _, l := range t.tul {
err := l.Close()
if err != nil {
errs = append(errs, fmt.Errorf("close udp listener %s err: %w", l.Address(), err))
}
}
return err
}
// Address implements constant.InboundListener
func (t *Tunnel) Address() string {
if t.ttl != nil {
return t.ttl.Address()
}
if t.tul != nil {
return t.tul.Address()
}
return ""
}
// Listen implements constant.InboundListener
func (t *Tunnel) Listen(tunnel C.Tunnel) error {
var err error
for _, network := range t.config.Network {
switch network {
case "tcp":
if t.ttl, err = LT.New(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err
}
case "udp":
if t.tul, err = LT.NewUDP(t.RawAddress(), t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...); err != nil {
return err
}
default:
log.Warnln("unknown network type: %s, passed", network)
continue
}
log.Infoln("Tunnel[%s](%s/%s)proxy listening at: %s", t.Name(), network, t.config.Target, t.Address())
if len(errs) > 0 {
return errors.Join(errs...)
}
return nil
}
// Address implements constant.InboundListener
func (t *Tunnel) Address() string {
var addrList []string
for _, l := range t.ttl {
addrList = append(addrList, "tcp://"+l.Address())
}
for _, l := range t.tul {
addrList = append(addrList, "udp://"+l.Address())
}
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (t *Tunnel) Listen(tunnel C.Tunnel) error {
for _, addr := range strings.Split(t.RawAddress(), ",") {
for _, network := range t.config.Network {
switch network {
case "tcp":
ttl, err := LT.New(addr, t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...)
if err != nil {
return err
}
t.ttl = append(t.ttl, ttl)
case "udp":
tul, err := LT.NewUDP(addr, t.config.Target, t.config.SpecialProxy, tunnel, t.Additions()...)
if err != nil {
return err
}
t.tul = append(t.tul, tul)
default:
log.Warnln("unknown network type: %s, passed", network)
continue
}
}
}
log.Infoln("Tunnel[%s](%s)proxy listening at: %s", t.Name(), t.config.Target, t.Address())
return nil
}
var _ C.InboundListener = (*Tunnel)(nil)

102
listener/inbound/vless.go Normal file
View File

@@ -0,0 +1,102 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_vless"
"github.com/metacubex/mihomo/log"
)
type VlessOption struct {
BaseOption
Users []VlessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
}
type VlessUser struct {
Username string `inbound:"username,omitempty"`
UUID string `inbound:"uuid"`
Flow string `inbound:"flow,omitempty"`
}
func (o VlessOption) Equal(config C.InboundConfig) bool {
return optionToString(o) == optionToString(config)
}
type Vless struct {
*Base
config *VlessOption
l C.MultiAddrListener
vs LC.VlessServer
}
func NewVless(options *VlessOption) (*Vless, error) {
base, err := NewBase(&options.BaseOption)
if err != nil {
return nil, err
}
users := make([]LC.VlessUser, len(options.Users))
for i, v := range options.Users {
users[i] = LC.VlessUser{
Username: v.Username,
UUID: v.UUID,
Flow: v.Flow,
}
}
return &Vless{
Base: base,
config: options,
vs: LC.VlessServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
},
}, nil
}
// Config implements constant.InboundListener
func (v *Vless) Config() C.InboundConfig {
return v.config
}
// Address implements constant.InboundListener
func (v *Vless) 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 *Vless) Listen(tunnel C.Tunnel) error {
var err error
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err
}
log.Infoln("Vless[%s] proxy listening at: %s", v.Name(), v.Address())
return nil
}
// Close implements constant.InboundListener
func (v *Vless) Close() error {
return v.l.Close()
}
var _ C.InboundListener = (*Vless)(nil)

View File

@@ -1,6 +1,8 @@
package inbound
import (
"strings"
C "github.com/metacubex/mihomo/constant"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing_vmess"
@@ -9,11 +11,13 @@ import (
type VmessOption struct {
BaseOption
Users []VmessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
Users []VmessUser `inbound:"users"`
WsPath string `inbound:"ws-path,omitempty"`
GrpcServiceName string `inbound:"grpc-service-name,omitempty"`
Certificate string `inbound:"certificate,omitempty"`
PrivateKey string `inbound:"private-key,omitempty"`
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
MuxOption MuxOption `inbound:"mux-option,omitempty"`
}
type VmessUser struct {
@@ -50,13 +54,15 @@ func NewVmess(options *VmessOption) (*Vmess, error) {
Base: base,
config: options,
vs: LC.VmessServer{
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
MuxOption: options.MuxOption.Build(),
Enable: true,
Listen: base.RawAddress(),
Users: users,
WsPath: options.WsPath,
GrpcServiceName: options.GrpcServiceName,
Certificate: options.Certificate,
PrivateKey: options.PrivateKey,
RealityConfig: options.RealityConfig.Build(),
MuxOption: options.MuxOption.Build(),
},
}, nil
}
@@ -68,25 +74,18 @@ func (v *Vmess) Config() C.InboundConfig {
// Address implements constant.InboundListener
func (v *Vmess) Address() string {
var addrList []string
if v.l != nil {
for _, addr := range v.l.AddrList() {
return addr.String()
addrList = append(addrList, addr.String())
}
}
return ""
return strings.Join(addrList, ",")
}
// Listen implements constant.InboundListener
func (v *Vmess) Listen(tunnel C.Tunnel) error {
var err error
users := make([]LC.VmessUser, len(v.config.Users))
for i, v := range v.config.Users {
users[i] = LC.VmessUser{
Username: v.Username,
UUID: v.UUID,
AlterID: v.AlterID,
}
}
v.l, err = sing_vmess.New(v.vs, tunnel, v.Additions()...)
if err != nil {
return err

View File

@@ -1,6 +1,8 @@
package mixed
import (
"crypto/tls"
"errors"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
@@ -8,7 +10,9 @@ import (
"github.com/metacubex/mihomo/component/auth"
C "github.com/metacubex/mihomo/constant"
authStore "github.com/metacubex/mihomo/listener/auth"
LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/http"
"github.com/metacubex/mihomo/listener/reality"
"github.com/metacubex/mihomo/listener/socks"
"github.com/metacubex/mihomo/transport/socks4"
"github.com/metacubex/mihomo/transport/socks5"
@@ -37,10 +41,10 @@ func (l *Listener) Close() 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...)
}
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
if len(additions) == 0 {
isDefault = true
@@ -50,14 +54,40 @@ 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 {
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)
}
ml := &Listener{
listener: l,
addr: addr,
addr: config.Listen,
}
go func() {
for {
@@ -68,7 +98,7 @@ func NewWithAuthenticator(addr string, tunnel C.Tunnel, store auth.AuthStore, ad
}
continue
}
store := store
store := config.AuthStore
if isDefault || store == authStore.Default { // only apply on default listener
if !inbound.IsRemoteAddrDisAllowed(c.RemoteAddr()) {
_ = c.Close()

View File

@@ -86,6 +86,20 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
return nil, err
}
listener, err = IN.NewVmess(vmessOption)
case "vless":
vlessOption := &IN.VlessOption{}
err = decoder.Decode(mapping, vlessOption)
if err != nil {
return nil, err
}
listener, err = IN.NewVless(vlessOption)
case "trojan":
trojanOption := &IN.TrojanOption{}
err = decoder.Decode(mapping, trojanOption)
if err != nil {
return nil, err
}
listener, err = IN.NewTrojan(trojanOption)
case "hysteria2":
hysteria2Option := &IN.Hysteria2Option{}
err = decoder.Decode(mapping, hysteria2Option)
@@ -106,6 +120,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
return nil, err
}
listener, err = IN.NewTuic(tuicOption)
case "anytls":
anytlsOption := &IN.AnyTLSOption{}
err = decoder.Decode(mapping, anytlsOption)
if err != nil {
return nil, err
}
listener, err = IN.NewAnyTLS(anytlsOption)
default:
return nil, fmt.Errorf("unsupport proxy type: %s", proxyType)
}

104
listener/reality/reality.go Normal file
View File

@@ -0,0 +1,104 @@
package reality
import (
"context"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"net"
"time"
"github.com/metacubex/mihomo/listener/inner"
"github.com/metacubex/mihomo/ntp"
"github.com/metacubex/reality"
)
type Conn = reality.Conn
type Config struct {
Dest string
PrivateKey string
ShortID []string
ServerNames []string
MaxTimeDifference int
Proxy string
}
func (c Config) Build() (*Builder, error) {
realityConfig := &reality.Config{}
realityConfig.SessionTicketsDisabled = true
realityConfig.Type = "tcp"
realityConfig.Dest = c.Dest
realityConfig.Time = ntp.Now
realityConfig.ServerNames = make(map[string]bool)
for _, it := range c.ServerNames {
realityConfig.ServerNames[it] = true
}
privateKey, err := base64.RawURLEncoding.DecodeString(c.PrivateKey)
if err != nil {
return nil, fmt.Errorf("decode private key: %w", err)
}
if len(privateKey) != 32 {
return nil, errors.New("invalid private key")
}
realityConfig.PrivateKey = privateKey
realityConfig.MaxTimeDiff = time.Duration(c.MaxTimeDifference) * time.Microsecond
realityConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range c.ShortID {
var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
if err != nil {
return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err)
}
if decodedLen > 8 {
return nil, fmt.Errorf("invalid short_id[%d]: %s", i, shortIDString)
}
realityConfig.ShortIds[shortID] = true
}
realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return inner.HandleTcp(address, c.Proxy)
}
return &Builder{realityConfig}, nil
}
type Builder struct {
realityConfig *reality.Config
}
func (b Builder) NewListener(l net.Listener) net.Listener {
l = reality.NewListener(l, b.realityConfig)
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly.
l = realityListenerWrapper{l}
return l
}
type realityConnWrapper struct {
*reality.Conn
}
func (c realityConnWrapper) Upstream() any {
return c.Conn
}
func (c realityConnWrapper) CloseWrite() error {
return c.Close()
}
type realityListenerWrapper struct {
net.Listener
}
func (l realityListenerWrapper) Accept() (net.Conn, error) {
c, err := l.Listener.Accept()
if err != nil {
return nil, err
}
return realityConnWrapper{c.(*reality.Conn)}, nil
}

View File

@@ -8,6 +8,7 @@ import (
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/shadowsocks/core"
"github.com/metacubex/mihomo/transport/socks5"
)
@@ -18,6 +19,7 @@ type Listener struct {
listeners []net.Listener
udpListeners []*UDPListener
pickCipher core.Cipher
handler *sing.ListenerHandler
}
var _listener *Listener
@@ -28,7 +30,17 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
return nil, err
}
sl := &Listener{false, config, nil, nil, pickCipher}
h, err := sing.NewListenerHandler(sing.ListenerConfig{
Tunnel: tunnel,
Type: C.SHADOWSOCKS,
Additions: additions,
MuxOption: config.MuxOption,
})
if err != nil {
return nil, err
}
sl := &Listener{false, config, nil, nil, pickCipher, h}
_listener = sl
for _, addr := range strings.Split(config.Listen, ",") {
@@ -107,7 +119,8 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
_ = conn.Close()
return
}
tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...))
l.handler.HandleSocket(target, conn, additions...)
//tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.SHADOWSOCKS, additions...))
}
func HandleShadowSocks(conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) bool {

View File

@@ -18,13 +18,12 @@ type UDPListener struct {
}
func NewUDP(addr string, pickCipher core.Cipher, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPListener, error) {
l, err := net.ListenPacket("udp", addr)
l, err := inbound.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
err = sockopt.UDPReuseaddr(l.(*net.UDPConn))
if err != nil {
if err := sockopt.UDPReuseaddr(l); err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err)
}

View File

@@ -136,8 +136,8 @@ func (h *ListenerHandler) NewConnection(ctx context.Context, conn net.Conn, meta
cMetadata.RawDstAddr = metadata.Destination.Unwrap().TCPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(metadata.Destination), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr()))
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleTCPConn(conn, cMetadata) // this goroutine must exit after conn unused
return nil
@@ -198,8 +198,8 @@ func (h *ListenerHandler) NewPacketConnection(ctx context.Context, conn network.
cMetadata.RawDstAddr = dest.Unwrap().UDPAddr()
}
inbound.ApplyAdditions(cMetadata, inbound.WithDstAddr(dest), inbound.WithSrcAddr(metadata.Source), inbound.WithInAddr(conn.LocalAddr()))
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
inbound.ApplyAdditions(cMetadata, h.Additions...)
inbound.ApplyAdditions(cMetadata, getAdditions(ctx)...)
h.Tunnel.HandleUDPPacket(cPacket, cMetadata)
}

24
listener/sing/util.go Normal file
View File

@@ -0,0 +1,24 @@
package sing
import (
"context"
"net"
"github.com/metacubex/mihomo/adapter/inbound"
"github.com/metacubex/mihomo/transport/socks5"
)
// HandleSocket like inbound.NewSocket combine with Tunnel.HandleTCPConn but also handel specialFqdn
func (h *ListenerHandler) HandleSocket(target socks5.Addr, conn net.Conn, _additions ...inbound.Addition) {
conn, metadata := inbound.NewSocket(target, conn, h.Type, h.Additions...)
if h.IsSpecialFqdn(metadata.Host) {
_ = h.ParseSpecialFqdn(
WithAdditions(context.Background(), _additions...),
conn,
ConvertMetadata(metadata),
)
} else {
inbound.ApplyAdditions(metadata, _additions...)
h.Tunnel.HandleTCPConn(conn, metadata)
}
}

View File

@@ -22,6 +22,7 @@ import (
"github.com/metacubex/sing-quic/hysteria2"
"github.com/metacubex/quic-go"
E "github.com/sagernet/sing/common/exceptions"
)
@@ -110,6 +111,13 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
config.UdpMTU = 1200 - 3
}
quicConfig := &quic.Config{
InitialStreamReceiveWindow: config.InitialStreamReceiveWindow,
MaxStreamReceiveWindow: config.MaxStreamReceiveWindow,
InitialConnectionReceiveWindow: config.InitialConnectionReceiveWindow,
MaxConnectionReceiveWindow: config.MaxConnectionReceiveWindow,
}
service, err := hysteria2.NewService[string](hysteria2.ServiceOptions{
Context: context.Background(),
Logger: log.SingLogger,
@@ -117,6 +125,7 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
ReceiveBPS: outbound.StringToBps(config.Down),
SalamanderPassword: salamanderPassword,
TLSConfig: tlsConfig,
QUICConfig: quicConfig,
IgnoreClientBandwidth: config.IgnoreClientBandwidth,
Handler: h,
MasqueradeHandler: masqueradeHandler,
@@ -140,13 +149,12 @@ func New(config LC.Hysteria2Server, tunnel C.Tunnel, additions ...inbound.Additi
_service := *service
service := &_service // make a copy
ul, err := net.ListenPacket("udp", addr)
ul, err := inbound.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
err = sockopt.UDPReuseaddr(ul.(*net.UDPConn))
if err != nil {
if err := sockopt.UDPReuseaddr(ul); err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err)
}

View File

@@ -82,13 +82,12 @@ func New(config LC.ShadowsocksServer, tunnel C.Tunnel, additions ...inbound.Addi
if config.Udp {
//UDP
ul, err := net.ListenPacket("udp", addr)
ul, err := inbound.ListenPacket("udp", addr)
if err != nil {
return nil, err
}
err = sockopt.UDPReuseaddr(ul.(*net.UDPConn))
if err != nil {
if err := sockopt.UDPReuseaddr(ul); err != nil {
log.Warnln("Failed to Reuse UDP Address: %s", err)
}

Some files were not shown because too many files have changed in this diff Show More