mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-02-27 17:27:09 +00:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
025ff19fab | ||
|
|
f61534602b | ||
|
|
7b382611bb | ||
|
|
0f32c054f4 | ||
|
|
4f8b70c8c6 | ||
|
|
dcef78782b | ||
|
|
7c444a91d3 | ||
|
|
e3d4ec2476 | ||
|
|
14217e7847 | ||
|
|
68abb1348a | ||
|
|
dee5898e36 | ||
|
|
1e22f4daa9 | ||
|
|
a7a796bb30 | ||
|
|
ff89bf0ea0 | ||
|
|
801f3c35ce | ||
|
|
7ff046a455 | ||
|
|
0ed159e41d | ||
|
|
070eb3142b | ||
|
|
f318b80557 | ||
|
|
c0de3c0e42 | ||
|
|
4bd3ae52bd | ||
|
|
00e6466153 | ||
|
|
c94b4421e5 | ||
|
|
8bc6f77e36 | ||
|
|
a7e56f1c43 | ||
|
|
05e8f13a8d | ||
|
|
136d114196 | ||
|
|
938ab7f44d | ||
|
|
a00f4f1108 | ||
|
|
1213023f11 | ||
|
|
3b40bf76b7 | ||
|
|
1dc4155195 | ||
|
|
d81c19a7c8 | ||
|
|
e2140e62ca | ||
|
|
8d783c65c1 | ||
|
|
91324b76d2 | ||
|
|
e23f40a56b | ||
|
|
5830afcbde | ||
|
|
e2b75b35bb | ||
|
|
06b9e6c367 | ||
|
|
dc1145a484 | ||
|
|
b151e7d69c | ||
|
|
808fdcf624 | ||
|
|
9962a0d091 | ||
|
|
ef29e4501e | ||
|
|
d1d846f1ab | ||
|
|
eaaccbc6dd | ||
|
|
447c416391 | ||
|
|
6d24ca9ae6 | ||
|
|
b52b7537fc | ||
|
|
3cc67fd759 | ||
|
|
9074b78e36 | ||
|
|
ccc3f84da2 | ||
|
|
9bfb10d7ae | ||
|
|
0a5ea37c07 | ||
|
|
a440f64080 | ||
|
|
0ac6c3b185 | ||
|
|
b69e52d4d7 | ||
|
|
9c73b5b750 | ||
|
|
fc233184fd | ||
|
|
192d769f75 | ||
|
|
c99c71a969 | ||
|
|
c7661d7765 | ||
|
|
56c128880c | ||
|
|
f4806b49b4 | ||
|
|
49d54cc293 | ||
|
|
1c5f4a3ab1 | ||
|
|
368b1e1296 | ||
|
|
a9ce5da09d | ||
|
|
301c78ff9a | ||
|
|
72a126e580 | ||
|
|
20739f5db7 | ||
|
|
89dfabe9b3 | ||
|
|
5a9ad0ed3c | ||
|
|
bb803249fa | ||
|
|
3f6823ba49 | ||
|
|
c786b72030 | ||
|
|
269c52575c | ||
|
|
c7fc93df37 | ||
|
|
5d9d8f4d3b | ||
|
|
f3a43fe3a6 | ||
|
|
9a959202ed | ||
|
|
cd23112dc5 |
12
.github/mihomo.service
vendored
12
.github/mihomo.service
vendored
@@ -1,17 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 2s
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
|
||||
17
.github/mihomo@.service
vendored
Normal file
17
.github/mihomo@.service
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_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
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
56
.github/workflows/build.yml
vendored
56
.github/workflows/build.yml
vendored
@@ -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 }
|
||||
@@ -107,7 +117,7 @@ jobs:
|
||||
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.abi != '1' }}
|
||||
@@ -122,6 +132,24 @@ jobs:
|
||||
sudo tar zxf go1.23.0.linux-amd64-abi1.tar.gz -C /usr/local
|
||||
echo "/usr/local/go/bin" >> $GITHUB_PATH
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.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: |
|
||||
cd $(go env GOROOT)
|
||||
curl https://github.com/MetaCubeX/go/commit/2a406dc9f1ea7323d6ca9fccb2fe9ddebb6b1cc8.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/7b1fd7d39c6be0185fbe1d929578ab372ac5c632.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/979d6d8bab3823ff572ace26767fd2ce3cf351ae.diff | patch --verbose -p 1
|
||||
curl https://github.com/MetaCubeX/go/commit/ac3e93c061779dfefc0dd13a5b6e6f764a25621e.diff | patch --verbose -p 1
|
||||
|
||||
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||
# this patch file only works on golang1.23.x
|
||||
# that means after golang1.24 release it must be changed
|
||||
@@ -132,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
|
||||
@@ -248,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
|
||||
@@ -298,7 +329,6 @@ jobs:
|
||||
run: |
|
||||
echo ${VERSION} > version.txt
|
||||
shell: bash
|
||||
|
||||
- name: Archive production artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -309,6 +339,7 @@ jobs:
|
||||
mihomo*.rpm
|
||||
mihomo*.zip
|
||||
version.txt
|
||||
checksums.txt
|
||||
|
||||
Upload-Prerelease:
|
||||
permissions: write-all
|
||||
@@ -322,6 +353,13 @@ jobs:
|
||||
path: bin/
|
||||
merge-multiple: true
|
||||
|
||||
- name: Calculate checksums
|
||||
run: |
|
||||
cd bin/
|
||||
find . -type f -not -name "checksums.*" -not -name "version.txt" | sort | xargs sha256sum > checksums.txt
|
||||
cat checksums.txt
|
||||
shell: bash
|
||||
|
||||
- name: Delete current release assets
|
||||
uses: 8Mi-Tech/delete-release-assets-action@main
|
||||
with:
|
||||
|
||||
@@ -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.**
|
||||
@@ -43,7 +43,7 @@ func MPTCP() bool {
|
||||
return getMultiPathTCP(&lc.ListenConfig)
|
||||
}
|
||||
|
||||
func ListenContext(ctx context.Context, network, address string) (net.Listener, error) {
|
||||
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 {
|
||||
@@ -59,11 +59,19 @@ func ListenContext(ctx context.Context, network, address string) (net.Listener,
|
||||
break
|
||||
default:
|
||||
if _, err := netip.ParseAddr(host); err != nil { // not ip
|
||||
return nil, fmt.Errorf("invalid network address: %s", address)
|
||||
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()
|
||||
@@ -74,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()
|
||||
|
||||
140
adapter/outbound/anytls.go
Normal file
140
adapter/outbound/anytls.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package outbound
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
"github.com/sagernet/sing/common/uot"
|
||||
)
|
||||
|
||||
type AnyTLS struct {
|
||||
*Base
|
||||
client *anytls.Client
|
||||
dialer proxydialer.SingDialer
|
||||
option *AnyTLSOption
|
||||
}
|
||||
|
||||
type AnyTLSOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port"`
|
||||
Password string `proxy:"password"`
|
||||
ALPN []string `proxy:"alpn,omitempty"`
|
||||
SNI string `proxy:"sni,omitempty"`
|
||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"`
|
||||
Fingerprint string `proxy:"fingerprint,omitempty"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
IdleSessionCheckInterval int `proxy:"idle-session-check-interval,omitempty"`
|
||||
IdleSessionTimeout int `proxy:"idle-session-timeout,omitempty"`
|
||||
MinIdleSession int `proxy:"min-idle-session,omitempty"`
|
||||
}
|
||||
|
||||
func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := t.client.CreateProxy(ctx, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(CN.NewRefConn(c, t), t), nil
|
||||
}
|
||||
|
||||
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
// create tcp
|
||||
options := t.Base.DialOptions(opts...)
|
||||
t.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := t.client.CreateProxy(ctx, uot.RequestDestination(2))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create uot on tcp
|
||||
if !metadata.Resolved() {
|
||||
ip, err := resolver.ResolveIP(ctx, metadata.Host)
|
||||
if err != nil {
|
||||
return nil, errors.New("can't resolve ip")
|
||||
}
|
||||
metadata.DstIP = ip
|
||||
}
|
||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), t), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (t *AnyTLS) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
|
||||
info := t.Base.ProxyInfo()
|
||||
info.DialerProxy = t.option.DialerProxy
|
||||
return info
|
||||
}
|
||||
|
||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
singDialer := proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer())
|
||||
|
||||
tOption := anytls.ClientConfig{
|
||||
Password: option.Password,
|
||||
Server: M.ParseSocksaddrHostPort(option.Server, uint16(option.Port)),
|
||||
Dialer: singDialer,
|
||||
IdleSessionCheckInterval: time.Duration(option.IdleSessionCheckInterval) * time.Second,
|
||||
IdleSessionTimeout: time.Duration(option.IdleSessionTimeout) * time.Second,
|
||||
MinIdleSession: option.MinIdleSession,
|
||||
}
|
||||
tlsConfig := &vmess.TLSConfig{
|
||||
Host: option.SNI,
|
||||
SkipCertVerify: option.SkipCertVerify,
|
||||
NextProtos: option.ALPN,
|
||||
FingerPrint: option.Fingerprint,
|
||||
ClientFingerprint: option.ClientFingerprint,
|
||||
}
|
||||
if tlsConfig.Host == "" {
|
||||
tlsConfig.Host = option.Server
|
||||
}
|
||||
if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
|
||||
tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
outbound := &AnyTLS{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
addr: addr,
|
||||
tp: C.AnyTLS,
|
||||
udp: option.UDP,
|
||||
tfo: option.TFO,
|
||||
mpTcp: option.MPTCP,
|
||||
iface: option.Interface,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
client: anytls.NewClient(context.TODO(), tOption),
|
||||
option: &option,
|
||||
dialer: singDialer,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
|
||||
_ = o.client.Close()
|
||||
})
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
@@ -153,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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -145,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{
|
||||
@@ -156,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,
|
||||
|
||||
@@ -8,12 +8,15 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
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"
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"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"
|
||||
mierucommon "github.com/enfein/mieru/v3/apis/common"
|
||||
mierumodel "github.com/enfein/mieru/v3/apis/model"
|
||||
mierupb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -26,13 +29,15 @@ type Mieru struct {
|
||||
|
||||
type MieruOption struct {
|
||||
BasicOption
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Name string `proxy:"name"`
|
||||
Server string `proxy:"server"`
|
||||
Port int `proxy:"port,omitempty"`
|
||||
PortRange string `proxy:"port-range,omitempty"`
|
||||
Transport string `proxy:"transport"`
|
||||
UDP bool `proxy:"udp,omitempty"`
|
||||
UserName string `proxy:"username"`
|
||||
Password string `proxy:"password"`
|
||||
Multiplexing string `proxy:"multiplexing,omitempty"`
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@@ -48,6 +53,23 @@ func (m *Mieru) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||
return NewConn(c, m), nil
|
||||
}
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||
if err := m.ensureClientIsRunning(opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := m.client.DialContext(ctx, metadata.UDPAddr())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
|
||||
}
|
||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), m), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
func (m *Mieru) SupportUOT() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
func (m *Mieru) ProxyInfo() C.ProxyInfo {
|
||||
info := m.Base.ProxyInfo()
|
||||
@@ -111,7 +133,7 @@ func NewMieru(option MieruOption) (*Mieru, error) {
|
||||
addr: addr,
|
||||
iface: option.Interface,
|
||||
tp: C.Mieru,
|
||||
udp: false,
|
||||
udp: option.UDP,
|
||||
xudp: false,
|
||||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
@@ -205,7 +227,7 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
}
|
||||
}
|
||||
}
|
||||
return &mieruclient.ClientConfig{
|
||||
config := &mieruclient.ClientConfig{
|
||||
Profile: &mierupb.ClientProfile{
|
||||
ProfileName: proto.String(option.Name),
|
||||
User: &mierupb.User{
|
||||
@@ -214,7 +236,13 @@ func buildMieruClientConfig(option MieruOption) (*mieruclient.ClientConfig, erro
|
||||
},
|
||||
Servers: []*mierupb.ServerEndpoint{server},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
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 {
|
||||
@@ -258,6 +286,11 @@ func validateMieruOption(option MieruOption) error {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
gost "github.com/metacubex/mihomo/transport/gost-plugin"
|
||||
"github.com/metacubex/mihomo/transport/restls"
|
||||
obfs "github.com/metacubex/mihomo/transport/simple-obfs"
|
||||
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
|
||||
@@ -34,6 +35,7 @@ type ShadowSocks struct {
|
||||
obfsMode string
|
||||
obfsOption *simpleObfsOption
|
||||
v2rayOption *v2rayObfs.Option
|
||||
gostOption *gost.Option
|
||||
shadowTLSOption *shadowtls.ShadowTLSOption
|
||||
restlsConfig *restlsC.Config
|
||||
}
|
||||
@@ -71,6 +73,17 @@ type v2rayObfsOption struct {
|
||||
V2rayHttpUpgradeFastOpen bool `obfs:"v2ray-http-upgrade-fast-open,omitempty"`
|
||||
}
|
||||
|
||||
type gostObfsOption struct {
|
||||
Mode string `obfs:"mode"`
|
||||
Host string `obfs:"host,omitempty"`
|
||||
Path string `obfs:"path,omitempty"`
|
||||
TLS bool `obfs:"tls,omitempty"`
|
||||
Fingerprint string `obfs:"fingerprint,omitempty"`
|
||||
Headers map[string]string `obfs:"headers,omitempty"`
|
||||
SkipCertVerify bool `obfs:"skip-cert-verify,omitempty"`
|
||||
Mux bool `obfs:"mux,omitempty"`
|
||||
}
|
||||
|
||||
type shadowTLSOption struct {
|
||||
Password string `obfs:"password"`
|
||||
Host string `obfs:"host"`
|
||||
@@ -97,7 +110,13 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
||||
case "websocket":
|
||||
var err error
|
||||
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
|
||||
if ss.v2rayOption != nil {
|
||||
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
|
||||
} else if ss.gostOption != nil {
|
||||
c, err = gost.NewGostWebsocket(ctx, c, ss.gostOption)
|
||||
} else {
|
||||
return nil, fmt.Errorf("plugin options is required")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||
}
|
||||
@@ -236,10 +255,11 @@ 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
|
||||
var gostOption *gost.Option
|
||||
var obfsOption *simpleObfsOption
|
||||
var shadowTLSOpt *shadowtls.ShadowTLSOption
|
||||
var restlsConfig *restlsC.Config
|
||||
@@ -281,6 +301,28 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
v2rayOption.SkipCertVerify = opts.SkipCertVerify
|
||||
v2rayOption.Fingerprint = opts.Fingerprint
|
||||
}
|
||||
} else if option.Plugin == "gost-plugin" {
|
||||
opts := gostObfsOption{Host: "bing.com", Mux: true}
|
||||
if err := decoder.Decode(option.PluginOpts, &opts); err != nil {
|
||||
return nil, fmt.Errorf("ss %s initialize gost-plugin error: %w", addr, err)
|
||||
}
|
||||
|
||||
if opts.Mode != "websocket" {
|
||||
return nil, fmt.Errorf("ss %s obfs mode error: %s", addr, opts.Mode)
|
||||
}
|
||||
obfsMode = opts.Mode
|
||||
gostOption = &gost.Option{
|
||||
Host: opts.Host,
|
||||
Path: opts.Path,
|
||||
Headers: opts.Headers,
|
||||
Mux: opts.Mux,
|
||||
}
|
||||
|
||||
if opts.TLS {
|
||||
gostOption.TLS = true
|
||||
gostOption.SkipCertVerify = opts.SkipCertVerify
|
||||
gostOption.Fingerprint = opts.Fingerprint
|
||||
}
|
||||
} else if option.Plugin == shadowtls.Mode {
|
||||
obfsMode = shadowtls.Mode
|
||||
opt := &shadowTLSOption{
|
||||
@@ -336,6 +378,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
|
||||
option: &option,
|
||||
obfsMode: obfsMode,
|
||||
v2rayOption: v2rayOption,
|
||||
gostOption: gostOption,
|
||||
obfsOption: obfsOption,
|
||||
shadowTLSOption: shadowTLSOpt,
|
||||
restlsConfig: restlsConfig,
|
||||
|
||||
@@ -141,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
|
||||
|
||||
@@ -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"
|
||||
@@ -513,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,
|
||||
}
|
||||
|
||||
@@ -459,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 {
|
||||
@@ -507,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
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
if err == nil {
|
||||
c.AppendToChains(f)
|
||||
} else {
|
||||
f.onDialFailed(proxy.Type(), err)
|
||||
f.onDialFailed(proxy.Type(), err, f.healthCheck)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
@@ -45,7 +45,7 @@ func (f *Fallback) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||
if err == nil {
|
||||
f.onDialSuccess()
|
||||
} else {
|
||||
f.onDialFailed(proxy.Type(), err)
|
||||
f.onDialFailed(proxy.Type(), err, f.healthCheck)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package outboundgroup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -17,22 +18,26 @@ import (
|
||||
"github.com/metacubex/mihomo/tunnel"
|
||||
|
||||
"github.com/dlclark/regexp2"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type GroupBase struct {
|
||||
*outbound.Base
|
||||
filterRegs []*regexp2.Regexp
|
||||
excludeFilterReg *regexp2.Regexp
|
||||
excludeTypeArray []string
|
||||
providers []provider.ProxyProvider
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
failedTime time.Time
|
||||
failedTesting atomic.Bool
|
||||
proxies [][]C.Proxy
|
||||
versions []atomic.Uint32
|
||||
TestTimeout int
|
||||
maxFailedTimes int
|
||||
filterRegs []*regexp2.Regexp
|
||||
excludeFilterRegs []*regexp2.Regexp
|
||||
excludeTypeArray []string
|
||||
providers []provider.ProxyProvider
|
||||
failedTestMux sync.Mutex
|
||||
failedTimes int
|
||||
failedTime time.Time
|
||||
failedTesting atomic.Bool
|
||||
TestTimeout int
|
||||
maxFailedTimes int
|
||||
|
||||
// for GetProxies
|
||||
getProxiesMutex sync.Mutex
|
||||
providerVersions []uint32
|
||||
providerProxies []C.Proxy
|
||||
}
|
||||
|
||||
type GroupBaseOption struct {
|
||||
@@ -46,15 +51,26 @@ type GroupBaseOption struct {
|
||||
}
|
||||
|
||||
func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
var excludeFilterReg *regexp2.Regexp
|
||||
if opt.excludeFilter != "" {
|
||||
excludeFilterReg = regexp2.MustCompile(opt.excludeFilter, regexp2.None)
|
||||
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 excludeTypeArray []string
|
||||
if opt.excludeType != "" {
|
||||
excludeTypeArray = strings.Split(opt.excludeType, "|")
|
||||
}
|
||||
|
||||
var excludeFilterRegs []*regexp2.Regexp
|
||||
if opt.excludeFilter != "" {
|
||||
for _, excludeFilter := range strings.Split(opt.excludeFilter, "`") {
|
||||
excludeFilterReg := regexp2.MustCompile(excludeFilter, regexp2.None)
|
||||
excludeFilterRegs = append(excludeFilterRegs, excludeFilterReg)
|
||||
}
|
||||
}
|
||||
|
||||
var filterRegs []*regexp2.Regexp
|
||||
if opt.filter != "" {
|
||||
for _, filter := range strings.Split(opt.filter, "`") {
|
||||
@@ -64,14 +80,14 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
}
|
||||
|
||||
gb := &GroupBase{
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filterRegs: filterRegs,
|
||||
excludeFilterReg: excludeFilterReg,
|
||||
excludeTypeArray: excludeTypeArray,
|
||||
providers: opt.providers,
|
||||
failedTesting: atomic.NewBool(false),
|
||||
TestTimeout: opt.TestTimeout,
|
||||
maxFailedTimes: opt.maxFailedTimes,
|
||||
Base: outbound.NewBase(opt.BaseOption),
|
||||
filterRegs: filterRegs,
|
||||
excludeFilterRegs: excludeFilterRegs,
|
||||
excludeTypeArray: excludeTypeArray,
|
||||
providers: opt.providers,
|
||||
failedTesting: atomic.NewBool(false),
|
||||
TestTimeout: opt.TestTimeout,
|
||||
maxFailedTimes: opt.maxFailedTimes,
|
||||
}
|
||||
|
||||
if gb.TestTimeout == 0 {
|
||||
@@ -81,9 +97,6 @@ func NewGroupBase(opt GroupBaseOption) *GroupBase {
|
||||
gb.maxFailedTimes = 5
|
||||
}
|
||||
|
||||
gb.proxies = make([][]C.Proxy, len(opt.providers))
|
||||
gb.versions = make([]atomic.Uint32, len(opt.providers))
|
||||
|
||||
return gb
|
||||
}
|
||||
|
||||
@@ -94,56 +107,55 @@ func (gb *GroupBase) Touch() {
|
||||
}
|
||||
|
||||
func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
providerVersions := make([]uint32, len(gb.providers))
|
||||
for i, pd := range gb.providers {
|
||||
if touch { // touch first
|
||||
pd.Touch()
|
||||
}
|
||||
providerVersions[i] = pd.Version()
|
||||
}
|
||||
|
||||
// thread safe
|
||||
gb.getProxiesMutex.Lock()
|
||||
defer gb.getProxiesMutex.Unlock()
|
||||
|
||||
// return the cached proxies if version not changed
|
||||
if slices.Equal(providerVersions, gb.providerVersions) {
|
||||
return gb.providerProxies
|
||||
}
|
||||
|
||||
var proxies []C.Proxy
|
||||
if len(gb.filterRegs) == 0 {
|
||||
for _, pd := range gb.providers {
|
||||
if touch {
|
||||
pd.Touch()
|
||||
}
|
||||
proxies = append(proxies, pd.Proxies()...)
|
||||
}
|
||||
} else {
|
||||
for i, pd := range gb.providers {
|
||||
if touch {
|
||||
pd.Touch()
|
||||
}
|
||||
|
||||
if pd.VehicleType() == types.Compatible {
|
||||
gb.versions[i].Store(pd.Version())
|
||||
gb.proxies[i] = pd.Proxies()
|
||||
for _, pd := range gb.providers {
|
||||
if pd.VehicleType() == types.Compatible { // compatible provider unneeded filter
|
||||
proxies = append(proxies, pd.Proxies()...)
|
||||
continue
|
||||
}
|
||||
|
||||
version := gb.versions[i].Load()
|
||||
if version != pd.Version() && gb.versions[i].CompareAndSwap(version, pd.Version()) {
|
||||
var (
|
||||
proxies []C.Proxy
|
||||
newProxies []C.Proxy
|
||||
)
|
||||
|
||||
proxies = pd.Proxies()
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
var newProxies []C.Proxy
|
||||
proxiesSet := map[string]struct{}{}
|
||||
for _, filterReg := range gb.filterRegs {
|
||||
for _, p := range pd.Proxies() {
|
||||
name := p.Name()
|
||||
if mat, _ := filterReg.MatchString(name); mat {
|
||||
if _, ok := proxiesSet[name]; !ok {
|
||||
proxiesSet[name] = struct{}{}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gb.proxies[i] = newProxies
|
||||
}
|
||||
}
|
||||
|
||||
for _, p := range gb.proxies {
|
||||
proxies = append(proxies, p...)
|
||||
proxies = append(proxies, newProxies...)
|
||||
}
|
||||
}
|
||||
|
||||
// Multiple filers means that proxies are sorted in the order in which the filers appear.
|
||||
// Although the filter has been performed once in the previous process,
|
||||
// when there are multiple providers, the array needs to be reordered as a whole.
|
||||
if len(gb.providers) > 1 && len(gb.filterRegs) > 1 {
|
||||
var newProxies []C.Proxy
|
||||
proxiesSet := map[string]struct{}{}
|
||||
@@ -167,32 +179,31 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
}
|
||||
proxies = newProxies
|
||||
}
|
||||
if gb.excludeTypeArray != nil {
|
||||
var newProxies []C.Proxy
|
||||
for _, p := range proxies {
|
||||
mType := p.Type().String()
|
||||
flag := false
|
||||
for i := range gb.excludeTypeArray {
|
||||
if strings.EqualFold(mType, gb.excludeTypeArray[i]) {
|
||||
flag = true
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
if flag {
|
||||
continue
|
||||
if len(gb.excludeFilterRegs) > 0 {
|
||||
var newProxies []C.Proxy
|
||||
LOOP1:
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
for _, excludeFilterReg := range gb.excludeFilterRegs {
|
||||
if mat, _ := excludeFilterReg.MatchString(name); mat {
|
||||
continue LOOP1
|
||||
}
|
||||
}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
proxies = newProxies
|
||||
}
|
||||
|
||||
if gb.excludeFilterReg != nil {
|
||||
if gb.excludeTypeArray != nil {
|
||||
var newProxies []C.Proxy
|
||||
LOOP2:
|
||||
for _, p := range proxies {
|
||||
name := p.Name()
|
||||
if mat, _ := gb.excludeFilterReg.MatchString(name); mat {
|
||||
continue
|
||||
mType := p.Type().String()
|
||||
for _, excludeType := range gb.excludeTypeArray {
|
||||
if strings.EqualFold(mType, excludeType) {
|
||||
continue LOOP2
|
||||
}
|
||||
}
|
||||
newProxies = append(newProxies, p)
|
||||
}
|
||||
@@ -200,9 +211,13 @@ func (gb *GroupBase) GetProxies(touch bool) []C.Proxy {
|
||||
}
|
||||
|
||||
if len(proxies) == 0 {
|
||||
return append(proxies, tunnel.Proxies()["COMPATIBLE"])
|
||||
return []C.Proxy{tunnel.Proxies()["COMPATIBLE"]}
|
||||
}
|
||||
|
||||
// only cache when proxies not empty
|
||||
gb.providerVersions = providerVersions
|
||||
gb.providerProxies = proxies
|
||||
|
||||
return proxies
|
||||
}
|
||||
|
||||
@@ -234,17 +249,21 @@ func (gb *GroupBase) URLTest(ctx context.Context, url string, expectedStatus uti
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
|
||||
func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error, fn func()) {
|
||||
if adapterType == C.Direct || adapterType == C.Compatible || adapterType == C.Reject || adapterType == C.Pass || adapterType == C.RejectDrop {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
go gb.healthCheck()
|
||||
if errors.Is(err, C.ErrNotSupport) {
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
fn()
|
||||
return
|
||||
}
|
||||
|
||||
gb.failedTestMux.Lock()
|
||||
defer gb.failedTestMux.Unlock()
|
||||
|
||||
@@ -261,7 +280,7 @@ func (gb *GroupBase) onDialFailed(adapterType C.AdapterType, err error) {
|
||||
log.Debugln("ProxyGroup: %s failed count: %d", gb.Name(), gb.failedTimes)
|
||||
if gb.failedTimes >= gb.maxFailedTimes {
|
||||
log.Warnln("because %s failed multiple times, active health check", gb.Name())
|
||||
gb.healthCheck()
|
||||
fn()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -95,7 +95,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
|
||||
if err == nil {
|
||||
c.AppendToChains(lb)
|
||||
} else {
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
lb.onDialFailed(proxy.Type(), err, lb.healthCheck)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
@@ -103,7 +103,7 @@ func (lb *LoadBalance) DialContext(ctx context.Context, metadata *C.Metadata, op
|
||||
if err == nil {
|
||||
lb.onDialSuccess()
|
||||
} else {
|
||||
lb.onDialFailed(proxy.Type(), err)
|
||||
lb.onDialFailed(proxy.Type(), err, lb.healthCheck)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
@@ -54,13 +52,13 @@ func (u *URLTest) Set(name string) error {
|
||||
if p == nil {
|
||||
return errors.New("proxy not exist")
|
||||
}
|
||||
u.selected = name
|
||||
u.fast(false)
|
||||
u.ForceSet(name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *URLTest) ForceSet(name string) {
|
||||
u.selected = name
|
||||
u.fastSingle.Reset()
|
||||
}
|
||||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
@@ -70,7 +68,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
if err == nil {
|
||||
c.AppendToChains(u)
|
||||
} else {
|
||||
u.onDialFailed(proxy.Type(), err)
|
||||
u.onDialFailed(proxy.Type(), err, u.healthCheck)
|
||||
}
|
||||
|
||||
if N.NeedHandshake(c) {
|
||||
@@ -78,7 +76,7 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
if err == nil {
|
||||
u.onDialSuccess()
|
||||
} else {
|
||||
u.onDialFailed(proxy.Type(), err)
|
||||
u.onDialFailed(proxy.Type(), err, u.healthCheck)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -88,9 +86,12 @@ func (u *URLTest) DialContext(ctx context.Context, metadata *C.Metadata, opts ..
|
||||
|
||||
// ListenPacketContext implements C.ProxyAdapter
|
||||
func (u *URLTest) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||
pc, err := u.fast(true).ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
proxy := u.fast(true)
|
||||
pc, err := proxy.ListenPacketContext(ctx, metadata, u.Base.DialOptions(opts...)...)
|
||||
if err == nil {
|
||||
pc.AppendToChains(u)
|
||||
} else {
|
||||
u.onDialFailed(proxy.Type(), err, u.healthCheck)
|
||||
}
|
||||
|
||||
return pc, err
|
||||
@@ -101,22 +102,27 @@ func (u *URLTest) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
||||
return u.fast(touch)
|
||||
}
|
||||
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
func (u *URLTest) healthCheck() {
|
||||
u.fastSingle.Reset()
|
||||
u.GroupBase.healthCheck()
|
||||
u.fastSingle.Reset()
|
||||
}
|
||||
|
||||
proxies := u.GetProxies(touch)
|
||||
if u.selected != "" {
|
||||
for _, proxy := range proxies {
|
||||
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||
continue
|
||||
}
|
||||
if proxy.Name() == u.selected {
|
||||
u.fastNode = proxy
|
||||
return proxy
|
||||
func (u *URLTest) fast(touch bool) C.Proxy {
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
proxies := u.GetProxies(touch)
|
||||
if u.selected != "" {
|
||||
for _, proxy := range proxies {
|
||||
if !proxy.AliveForTestUrl(u.testUrl) {
|
||||
continue
|
||||
}
|
||||
if proxy.Name() == u.selected {
|
||||
u.fastNode = proxy
|
||||
return proxy, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elm, _, shared := u.fastSingle.Do(func() (C.Proxy, error) {
|
||||
fast := proxies[0]
|
||||
minDelay := fast.LastDelayForTestUrl(u.testUrl)
|
||||
fastNotExist := true
|
||||
@@ -182,31 +188,7 @@ func (u *URLTest) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
func (u *URLTest) URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (map[string]uint16, error) {
|
||||
var wg sync.WaitGroup
|
||||
var lock sync.Mutex
|
||||
mp := map[string]uint16{}
|
||||
proxies := u.GetProxies(false)
|
||||
for _, proxy := range proxies {
|
||||
proxy := proxy
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
delay, err := proxy.URLTest(ctx, u.testUrl, expectedStatus)
|
||||
if err == nil {
|
||||
lock.Lock()
|
||||
mp[proxy.Name()] = delay
|
||||
lock.Unlock()
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
if len(mp) == 0 {
|
||||
return mp, fmt.Errorf("get delay: all proxies timeout")
|
||||
} else {
|
||||
return mp, nil
|
||||
}
|
||||
return u.GroupBase.URLTest(ctx, u.testUrl, expectedStatus)
|
||||
}
|
||||
|
||||
func parseURLTestOption(config map[string]any) []urlTestOption {
|
||||
|
||||
@@ -148,6 +148,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -57,16 +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"`
|
||||
SizeLimit int64 `provider:"size-limit,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"`
|
||||
@@ -99,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":
|
||||
@@ -113,16 +119,13 @@ func ParseProxyProvider(name string, mapping map[string]any) (types.ProxyProvide
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -30,44 +31,102 @@ 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.version += 1
|
||||
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 +138,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,11 +157,176 @@ 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) {
|
||||
func NewProxySetProvider(name string, interval time.Duration, parser resource.Parser[[]C.Proxy], vehicle types.Vehicle, hc *HealthCheck) (*ProxySetProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &proxySetProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
},
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, parser, pd.setProxies)
|
||||
pd.Fetcher = fetcher
|
||||
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
|
||||
httpVehicle.SetInRead(func(resp *http.Response) {
|
||||
if subscriptionInfo := resp.Header.Get("subscription-userinfo"); subscriptionInfo != "" {
|
||||
cachefile.Cache().SetSubscriptionInfo(name, subscriptionInfo)
|
||||
pd.subscriptionInfo = NewSubscriptionInfo(subscriptionInfo)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (pp *ProxySetProvider) Close() error {
|
||||
runtime.SetFinalizer(pp, nil)
|
||||
return pp.proxySetProvider.Close()
|
||||
}
|
||||
|
||||
// InlineProvider for auto gc
|
||||
type InlineProvider struct {
|
||||
*inlineProvider
|
||||
}
|
||||
|
||||
type inlineProvider struct {
|
||||
baseProvider
|
||||
updateAt time.Time
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(providerForApi{
|
||||
Name: ip.Name(),
|
||||
Type: ip.Type().String(),
|
||||
VehicleType: ip.VehicleType().String(),
|
||||
Proxies: ip.Proxies(),
|
||||
TestUrl: ip.healthCheck.url,
|
||||
ExpectedStatus: ip.healthCheck.expectedStatus.String(),
|
||||
UpdatedAt: ip.updateAt,
|
||||
})
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) VehicleType() types.VehicleType {
|
||||
return types.Inline
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Initial() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ip *inlineProvider) Update() error {
|
||||
// make api update happy
|
||||
ip.updateAt = time.Now()
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewInlineProvider(name string, payload []map[string]any, parser resource.Parser[[]C.Proxy], hc *HealthCheck) (*InlineProvider, error) {
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
ps := ProxySchema{Proxies: payload}
|
||||
buf, err := yaml.Marshal(ps)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies, err := parser(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip := &inlineProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
updateAt: time.Now(),
|
||||
}
|
||||
wrapper := &InlineProvider{ip}
|
||||
runtime.SetFinalizer(wrapper, (*InlineProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (ip *InlineProvider) Close() error {
|
||||
runtime.SetFinalizer(ip, nil)
|
||||
return ip.baseProvider.Close()
|
||||
}
|
||||
|
||||
// CompatibleProvider for auto gc
|
||||
type CompatibleProvider struct {
|
||||
*compatibleProvider
|
||||
}
|
||||
|
||||
type compatibleProvider struct {
|
||||
baseProvider
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(providerForApi{
|
||||
Name: cp.Name(),
|
||||
Type: cp.Type().String(),
|
||||
VehicleType: cp.VehicleType().String(),
|
||||
Proxies: cp.Proxies(),
|
||||
TestUrl: cp.healthCheck.url,
|
||||
ExpectedStatus: cp.healthCheck.expectedStatus.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Initial() error {
|
||||
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
||||
cp.HealthCheck()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func NewCompatibleProvider(name string, proxies []C.Proxy, hc *HealthCheck) (*CompatibleProvider, error) {
|
||||
if len(proxies) == 0 {
|
||||
return nil, errors.New("provider need one proxy at least")
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
baseProvider: baseProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
},
|
||||
}
|
||||
|
||||
wrapper := &CompatibleProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvider) Close() error {
|
||||
runtime.SetFinalizer(cp, nil)
|
||||
return cp.compatibleProvider.Close()
|
||||
}
|
||||
|
||||
func 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)
|
||||
@@ -163,151 +345,6 @@ func NewProxySetProvider(name string, interval time.Duration, filter string, exc
|
||||
filterRegs = append(filterRegs, filterReg)
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &proxySetProvider{
|
||||
proxies: []C.Proxy{},
|
||||
healthCheck: hc,
|
||||
}
|
||||
|
||||
fetcher := resource.NewFetcher[[]C.Proxy](name, interval, vehicle, proxiesParseAndFilter(filter, excludeFilter, excludeTypeArray, filterRegs, excludeFilterReg, dialerProxy, override), proxiesOnUpdate(pd))
|
||||
pd.Fetcher = fetcher
|
||||
wrapper := &ProxySetProvider{pd}
|
||||
if httpVehicle, ok := vehicle.(*resource.HTTPVehicle); ok {
|
||||
httpVehicle.SetProvider(wrapper)
|
||||
}
|
||||
runtime.SetFinalizer(wrapper, (*ProxySetProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (pp *ProxySetProvider) Close() error {
|
||||
runtime.SetFinalizer(pp, nil)
|
||||
return pp.proxySetProvider.Close()
|
||||
}
|
||||
|
||||
func (pp *ProxySetProvider) SetProvider(provider types.ProxyProvider) {
|
||||
pp.proxySetProvider.SetProvider(provider)
|
||||
}
|
||||
|
||||
// CompatibleProvider for auto gc
|
||||
type CompatibleProvider struct {
|
||||
*compatibleProvider
|
||||
}
|
||||
|
||||
type compatibleProvider struct {
|
||||
name string
|
||||
healthCheck *HealthCheck
|
||||
subscriptionInfo *SubscriptionInfo
|
||||
proxies []C.Proxy
|
||||
version uint32
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
"name": cp.Name(),
|
||||
"type": cp.Type().String(),
|
||||
"vehicleType": cp.VehicleType().String(),
|
||||
"proxies": cp.Proxies(),
|
||||
"testUrl": cp.healthCheck.url,
|
||||
"expectedStatus": cp.healthCheck.expectedStatus.String(),
|
||||
})
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Version() uint32 {
|
||||
return cp.version
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Name() string {
|
||||
return cp.name
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheck() {
|
||||
cp.healthCheck.check()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Initial() error {
|
||||
if cp.healthCheck.interval != 0 && cp.healthCheck.url != "" {
|
||||
cp.HealthCheck()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) VehicleType() types.VehicleType {
|
||||
return types.Compatible
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Type() types.ProviderType {
|
||||
return types.Proxy
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Proxies() []C.Proxy {
|
||||
return cp.proxies
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Count() int {
|
||||
return len(cp.proxies)
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Touch() {
|
||||
cp.healthCheck.touch()
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) HealthCheckURL() string {
|
||||
return cp.healthCheck.url
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) RegisterHealthCheckTask(url string, expectedStatus utils.IntRanges[uint16], filter string, interval uint) {
|
||||
cp.healthCheck.registerHealthCheckTask(url, expectedStatus, filter, interval)
|
||||
}
|
||||
|
||||
func (cp *compatibleProvider) Close() error {
|
||||
cp.healthCheck.close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (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")
|
||||
}
|
||||
|
||||
if hc.auto() {
|
||||
go hc.process()
|
||||
}
|
||||
|
||||
pd := &compatibleProvider{
|
||||
name: name,
|
||||
proxies: proxies,
|
||||
healthCheck: hc,
|
||||
}
|
||||
|
||||
wrapper := &CompatibleProvider{pd}
|
||||
runtime.SetFinalizer(wrapper, (*CompatibleProvider).Close)
|
||||
return wrapper, nil
|
||||
}
|
||||
|
||||
func (cp *CompatibleProvider) Close() error {
|
||||
runtime.SetFinalizer(cp, nil)
|
||||
return cp.compatibleProvider.Close()
|
||||
}
|
||||
|
||||
func proxiesOnUpdate(pd *proxySetProvider) func([]C.Proxy) {
|
||||
return func(elm []C.Proxy) {
|
||||
pd.setProxies(elm)
|
||||
pd.version += 1
|
||||
}
|
||||
}
|
||||
|
||||
func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray []string, filterRegs []*regexp2.Regexp, excludeFilterReg *regexp2.Regexp, dialerProxy string, override OverrideSchema) resource.Parser[[]C.Proxy] {
|
||||
return func(buf []byte) ([]C.Proxy, error) {
|
||||
schema := &ProxySchema{}
|
||||
|
||||
@@ -422,5 +459,5 @@ func proxiesParseAndFilter(filter string, excludeFilter string, excludeTypeArray
|
||||
}
|
||||
|
||||
return proxies, nil
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ func NewSubscriptionInfo(userinfo string) (si *SubscriptionInfo) {
|
||||
si.Expire = intValue
|
||||
}
|
||||
}
|
||||
|
||||
return si
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,11 @@ func (c *enhanceUDPConn) WaitReadFrom() (data []byte, put func(), addr net.Addr,
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port}
|
||||
case *syscall.SockaddrInet6:
|
||||
ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 16 bytes
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: strconv.FormatInt(int64(from.ZoneId), 10)}
|
||||
zone := ""
|
||||
if from.ZoneId != 0 {
|
||||
zone = strconv.FormatInt(int64(from.ZoneId), 10)
|
||||
}
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: zone}
|
||||
}
|
||||
}
|
||||
// udp should not convert readN == 0 to io.EOF
|
||||
|
||||
@@ -54,7 +54,11 @@ func (c *enhanceUDPConn) WaitReadFrom() (data []byte, put func(), addr net.Addr,
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port}
|
||||
case *windows.SockaddrInet6:
|
||||
ip := from.Addr // copy from.Addr; ip escapes, so this line allocates 16 bytes
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: strconv.FormatInt(int64(from.ZoneId), 10)}
|
||||
zone := ""
|
||||
if from.ZoneId != 0 {
|
||||
zone = strconv.FormatInt(int64(from.ZoneId), 10)
|
||||
}
|
||||
addr = &net.UDPAddr{IP: ip[:], Port: from.Port, Zone: zone}
|
||||
}
|
||||
}
|
||||
// udp should not convert readN == 0 to io.EOF
|
||||
|
||||
30
common/sockopt/reuse_common.go
Normal file
30
common/sockopt/reuse_common.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func RawConnReuseaddr(rc syscall.RawConn) (err error) {
|
||||
var innerErr error
|
||||
err = rc.Control(func(fd uintptr) {
|
||||
innerErr = reuseControl(fd)
|
||||
})
|
||||
|
||||
if innerErr != nil {
|
||||
err = innerErr
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func UDPReuseaddr(c net.PacketConn) error {
|
||||
if c, ok := c.(syscall.Conn); ok {
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return RawConnReuseaddr(rc)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,5 @@
|
||||
//go:build !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
|
||||
|
||||
package dialer
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(*net.ListenConfig) {}
|
||||
func reuseControl(fd uintptr) error { return nil }
|
||||
22
common/sockopt/reuse_unix.go
Normal file
22
common/sockopt/reuse_unix.go
Normal file
@@ -0,0 +1,22 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func reuseControl(fd uintptr) error {
|
||||
e1 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
e2 := unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
|
||||
if e1 != nil {
|
||||
return e1
|
||||
}
|
||||
|
||||
if e2 != nil {
|
||||
return e2
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
9
common/sockopt/reuse_windows.go
Normal file
9
common/sockopt/reuse_windows.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func reuseControl(fd uintptr) error {
|
||||
return windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||
rc, err := c.SyscallConn()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
rc.Control(func(fd uintptr) {
|
||||
err = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package sockopt
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
func UDPReuseaddr(c *net.UDPConn) (err error) {
|
||||
return
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -139,10 +140,34 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ranges IntRanges[T]) Merge() (mergedRanges IntRanges[T]) {
|
||||
if len(ranges) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Slice(ranges, func(i, j int) bool {
|
||||
return ranges[i].Start() < ranges[j].Start()
|
||||
})
|
||||
mergedRanges = ranges[:1]
|
||||
var rangeIndex int
|
||||
for _, r := range ranges[1:] {
|
||||
if mergedRanges[rangeIndex].End()+1 > mergedRanges[rangeIndex].End() && // integer overflow
|
||||
r.Start() > mergedRanges[rangeIndex].End()+1 {
|
||||
mergedRanges = append(mergedRanges, r)
|
||||
rangeIndex++
|
||||
} else if r.End() > mergedRanges[rangeIndex].End() {
|
||||
mergedRanges[rangeIndex].end = r.End()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
82
common/utils/ranges_test.go
Normal file
82
common/utils/ranges_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMergeRanges(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, testRange := range []struct {
|
||||
ranges IntRanges[uint16]
|
||||
expected IntRanges[uint16]
|
||||
}{
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](0, 1),
|
||||
NewRange[uint16](1, 2),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](0, 2),
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](0, 3),
|
||||
NewRange[uint16](5, 7),
|
||||
NewRange[uint16](8, 9),
|
||||
NewRange[uint16](10, 10),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](0, 3),
|
||||
NewRange[uint16](5, 10),
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 3),
|
||||
NewRange[uint16](2, 6),
|
||||
NewRange[uint16](8, 10),
|
||||
NewRange[uint16](15, 18),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 6),
|
||||
NewRange[uint16](8, 10),
|
||||
NewRange[uint16](15, 18),
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 3),
|
||||
NewRange[uint16](2, 7),
|
||||
NewRange[uint16](2, 6),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 7),
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 3),
|
||||
NewRange[uint16](2, 6),
|
||||
NewRange[uint16](2, 7),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 7),
|
||||
},
|
||||
},
|
||||
{
|
||||
ranges: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 3),
|
||||
NewRange[uint16](2, 65535),
|
||||
NewRange[uint16](2, 7),
|
||||
NewRange[uint16](3, 16),
|
||||
},
|
||||
expected: IntRanges[uint16]{
|
||||
NewRange[uint16](1, 65535),
|
||||
},
|
||||
},
|
||||
} {
|
||||
assert.Equal(t, testRange.expected, testRange.ranges.Merge())
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -88,12 +88,22 @@ func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
|
||||
socketHookToListenConfig(lc)
|
||||
} else {
|
||||
if cfg.interfaceName != "" {
|
||||
interfaceName := cfg.interfaceName
|
||||
if interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
||||
}
|
||||
}
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
interfaceName = ""
|
||||
}
|
||||
if interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(cfg.interfaceName, lc, network, address, rAddrPort)
|
||||
addr, err := bind(interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -153,12 +163,18 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
|
||||
socketHookToToDialer(dialer)
|
||||
} else {
|
||||
if opt.interfaceName != "" {
|
||||
interfaceName := opt.interfaceName // don't change the "opt", it's a pointer
|
||||
if interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
interfaceName = finder.FindInterfaceName(destination)
|
||||
}
|
||||
}
|
||||
if interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
if err := bind(interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -373,12 +389,7 @@ func (d Dialer) DialContext(ctx context.Context, network, address string) (net.C
|
||||
}
|
||||
|
||||
func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort) (net.PacketConn, error) {
|
||||
opt := d.Opt // make a copy
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
WithInterface("")(&opt)
|
||||
}
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(opt))
|
||||
return ListenPacket(ctx, ParseNetwork(network, rAddrPort.Addr()), address, rAddrPort, WithOption(d.Opt))
|
||||
}
|
||||
|
||||
func NewDialer(options ...Option) Dialer {
|
||||
|
||||
@@ -3,6 +3,7 @@ package dialer
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
@@ -12,8 +13,14 @@ var (
|
||||
DefaultOptions []Option
|
||||
DefaultInterface = atomic.NewTypedValue[string]("")
|
||||
DefaultRoutingMark = atomic.NewInt32(0)
|
||||
|
||||
DefaultInterfaceFinder = atomic.NewTypedValue[InterfaceFinder](nil)
|
||||
)
|
||||
|
||||
type InterfaceFinder interface {
|
||||
FindInterfaceName(destination netip.Addr) string
|
||||
}
|
||||
|
||||
type NetDialer interface {
|
||||
DialContext(ctx context.Context, network, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,11 @@ import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"github.com/metacubex/mihomo/common/sockopt"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
windows.SetsockoptInt(windows.Handle(fd), windows.SOL_SOCKET, windows.SO_REUSEADDR, 1)
|
||||
})
|
||||
return sockopt.RawConnReuseaddr(c)
|
||||
})
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris
|
||||
|
||||
package dialer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func addrReuseToListenConfig(lc *net.ListenConfig) {
|
||||
addControlToListenConfig(lc, func(ctx context.Context, network, address string, c syscall.RawConn) error {
|
||||
return c.Control(func(fd uintptr) {
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
|
||||
unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
|
||||
})
|
||||
})
|
||||
}
|
||||
37
component/generater/cmd.go
Normal file
37
component/generater/cmd.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package generater
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofrs/uuid/v5"
|
||||
)
|
||||
|
||||
func Main(args []string) {
|
||||
if len(args) < 1 {
|
||||
panic("Using: generate uuid/reality-keypair/wg-keypair")
|
||||
}
|
||||
switch args[0] {
|
||||
case "uuid":
|
||||
newUUID, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(newUUID.String())
|
||||
case "reality-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
publicKey := privateKey.PublicKey()
|
||||
fmt.Println("PrivateKey: " + base64.RawURLEncoding.EncodeToString(privateKey[:]))
|
||||
fmt.Println("PublicKey: " + base64.RawURLEncoding.EncodeToString(publicKey[:]))
|
||||
case "wg-keypair":
|
||||
privateKey, err := GeneratePrivateKey()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println("PrivateKey: " + privateKey.String())
|
||||
fmt.Println("PublicKey: " + privateKey.PublicKey().String())
|
||||
}
|
||||
}
|
||||
97
component/generater/types.go
Normal file
97
component/generater/types.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copy from https://github.com/WireGuard/wgctrl-go/blob/a9ab2273dd1075ea74b88c76f8757f8b4003fcbf/wgtypes/types.go#L71-L155
|
||||
|
||||
package generater
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/curve25519"
|
||||
)
|
||||
|
||||
// KeyLen is the expected key length for a WireGuard key.
|
||||
const KeyLen = 32 // wgh.KeyLen
|
||||
|
||||
// A Key is a public, private, or pre-shared secret key. The Key constructor
|
||||
// functions in this package can be used to create Keys suitable for each of
|
||||
// these applications.
|
||||
type Key [KeyLen]byte
|
||||
|
||||
// GenerateKey generates a Key suitable for use as a pre-shared secret key from
|
||||
// a cryptographically safe source.
|
||||
//
|
||||
// The output Key should not be used as a private key; use GeneratePrivateKey
|
||||
// instead.
|
||||
func GenerateKey() (Key, error) {
|
||||
b := make([]byte, KeyLen)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to read random bytes: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// GeneratePrivateKey generates a Key suitable for use as a private key from a
|
||||
// cryptographically safe source.
|
||||
func GeneratePrivateKey() (Key, error) {
|
||||
key, err := GenerateKey()
|
||||
if err != nil {
|
||||
return Key{}, err
|
||||
}
|
||||
|
||||
// Modify random bytes using algorithm described at:
|
||||
// https://cr.yp.to/ecdh.html.
|
||||
key[0] &= 248
|
||||
key[31] &= 127
|
||||
key[31] |= 64
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// NewKey creates a Key from an existing byte slice. The byte slice must be
|
||||
// exactly 32 bytes in length.
|
||||
func NewKey(b []byte) (Key, error) {
|
||||
if len(b) != KeyLen {
|
||||
return Key{}, fmt.Errorf("wgtypes: incorrect key size: %d", len(b))
|
||||
}
|
||||
|
||||
var k Key
|
||||
copy(k[:], b)
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
// ParseKey parses a Key from a base64-encoded string, as produced by the
|
||||
// Key.String method.
|
||||
func ParseKey(s string) (Key, error) {
|
||||
b, err := base64.StdEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return Key{}, fmt.Errorf("wgtypes: failed to parse base64-encoded key: %v", err)
|
||||
}
|
||||
|
||||
return NewKey(b)
|
||||
}
|
||||
|
||||
// PublicKey computes a public key from the private key k.
|
||||
//
|
||||
// PublicKey should only be called when k is a private key.
|
||||
func (k Key) PublicKey() Key {
|
||||
var (
|
||||
pub [KeyLen]byte
|
||||
priv = [KeyLen]byte(k)
|
||||
)
|
||||
|
||||
// ScalarBaseMult uses the correct base value per https://cr.yp.to/ecdh.html,
|
||||
// so no need to specify it.
|
||||
curve25519.ScalarBaseMult(&pub, &priv)
|
||||
|
||||
return Key(pub)
|
||||
}
|
||||
|
||||
// String returns the base64-encoded string representation of a Key.
|
||||
//
|
||||
// ParseKey can be used to produce a new Key from this string.
|
||||
func (k Key) String() string {
|
||||
return base64.StdEncoding.EncodeToString(k[:])
|
||||
}
|
||||
@@ -7,15 +7,17 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/singledo"
|
||||
|
||||
"github.com/metacubex/bart"
|
||||
)
|
||||
|
||||
type Interface struct {
|
||||
Index int
|
||||
MTU int
|
||||
Name string
|
||||
Addresses []netip.Prefix
|
||||
HardwareAddr net.HardwareAddr
|
||||
Flags net.Flags
|
||||
Addresses []netip.Prefix
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -23,16 +25,23 @@ var (
|
||||
ErrAddrNotFound = errors.New("addr not found")
|
||||
)
|
||||
|
||||
var interfaces = singledo.NewSingle[map[string]*Interface](time.Second * 20)
|
||||
type ifaceCache struct {
|
||||
ifMap map[string]*Interface
|
||||
ifTable bart.Table[*Interface]
|
||||
}
|
||||
|
||||
func Interfaces() (map[string]*Interface, error) {
|
||||
value, err, _ := interfaces.Do(func() (map[string]*Interface, error) {
|
||||
var caches = singledo.NewSingle[*ifaceCache](time.Second * 20)
|
||||
|
||||
func getCache() (*ifaceCache, error) {
|
||||
value, err, _ := caches.Do(func() (*ifaceCache, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := map[string]*Interface{}
|
||||
cache := &ifaceCache{
|
||||
ifMap: make(map[string]*Interface),
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
addrs, err := iface.Addrs()
|
||||
@@ -61,21 +70,34 @@ func Interfaces() (map[string]*Interface, error) {
|
||||
}
|
||||
}
|
||||
|
||||
r[iface.Name] = &Interface{
|
||||
ifaceObj := &Interface{
|
||||
Index: iface.Index,
|
||||
MTU: iface.MTU,
|
||||
Name: iface.Name,
|
||||
Addresses: ipNets,
|
||||
HardwareAddr: iface.HardwareAddr,
|
||||
Flags: iface.Flags,
|
||||
Addresses: ipNets,
|
||||
}
|
||||
cache.ifMap[iface.Name] = ifaceObj
|
||||
|
||||
for _, prefix := range ipNets {
|
||||
cache.ifTable.Insert(prefix, ifaceObj)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
return cache, nil
|
||||
})
|
||||
return value, err
|
||||
}
|
||||
|
||||
func Interfaces() (map[string]*Interface, error) {
|
||||
cache, err := getCache()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cache.ifMap, nil
|
||||
}
|
||||
|
||||
func ResolveInterface(name string) (*Interface, error) {
|
||||
ifaces, err := Interfaces()
|
||||
if err != nil {
|
||||
@@ -90,23 +112,29 @@ func ResolveInterface(name string) (*Interface, error) {
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func IsLocalIp(ip netip.Addr) (bool, error) {
|
||||
ifaces, err := Interfaces()
|
||||
func ResolveInterfaceByAddr(addr netip.Addr) (*Interface, error) {
|
||||
cache, err := getCache()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
iface, ok := cache.ifTable.Lookup(addr)
|
||||
if !ok {
|
||||
return nil, ErrIfaceNotFound
|
||||
}
|
||||
|
||||
return iface, nil
|
||||
}
|
||||
|
||||
func IsLocalIp(addr netip.Addr) (bool, error) {
|
||||
cache, err := getCache()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
for _, addr := range iface.Addresses {
|
||||
if addr.Contains(ip) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
return cache.ifTable.Contains(addr), nil
|
||||
}
|
||||
|
||||
func FlushCache() {
|
||||
interfaces.Reset()
|
||||
caches.Reset()
|
||||
}
|
||||
|
||||
func (iface *Interface) PickIPv4Addr(destination netip.Addr) (netip.Prefix, error) {
|
||||
|
||||
@@ -90,6 +90,7 @@ type HTTPVehicle struct {
|
||||
header http.Header
|
||||
timeout time.Duration
|
||||
sizeLimit int64
|
||||
inRead func(response *http.Response)
|
||||
provider types.ProxyProvider
|
||||
}
|
||||
|
||||
@@ -113,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) {
|
||||
@@ -140,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 {
|
||||
|
||||
@@ -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
|
||||
@@ -56,28 +60,35 @@ func (sd *Dispatcher) shouldOverride(metadata *C.Metadata) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter) bool {
|
||||
// UDPSniff is called when a UDP NAT is created and passed the first initialization packet.
|
||||
// It may return a wrapped packetSender if the sniffer process needs to wait for multiple packets.
|
||||
// This function must be non-blocking, and any blocking operations should be done in the wrapped packetSender.
|
||||
func (sd *Dispatcher) UDPSniff(packet C.PacketAdapter, packetSender C.PacketSender) C.PacketSender {
|
||||
metadata := packet.Metadata()
|
||||
if sd.shouldOverride(metadata) {
|
||||
for sniffer, config := range sd.sniffers {
|
||||
if sniffer.SupportNetwork() == C.UDP || sniffer.SupportNetwork() == C.ALLNet {
|
||||
inWhitelist := sniffer.SupportPort(metadata.DstPort)
|
||||
for current, config := range sd.sniffers {
|
||||
if current.SupportNetwork() == C.UDP || current.SupportNetwork() == C.ALLNet {
|
||||
inWhitelist := current.SupportPort(metadata.DstPort)
|
||||
overrideDest := config.OverrideDest
|
||||
|
||||
if inWhitelist {
|
||||
host, err := sniffer.SniffData(packet.Data())
|
||||
if wrapable, ok := current.(sniffer.MultiPacketSniffer); ok {
|
||||
return wrapable.WrapperSender(packetSender, overrideDest)
|
||||
}
|
||||
|
||||
host, err := current.SniffData(packet.Data())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
sd.replaceDomain(metadata, host, overrideDest)
|
||||
return true
|
||||
replaceDomain(metadata, host, overrideDest)
|
||||
return packetSender
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return packetSender
|
||||
}
|
||||
|
||||
// TCPSniff returns true if the connection is sniffed to have a domain
|
||||
@@ -98,16 +109,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
|
||||
}
|
||||
@@ -121,13 +137,13 @@ func (sd *Dispatcher) TCPSniff(conn *N.BufferedConn, metadata *C.Metadata) bool
|
||||
|
||||
sd.skipList.Delete(dst)
|
||||
|
||||
sd.replaceDomain(metadata, host, overrideDest)
|
||||
replaceDomain(metadata, host, overrideDest)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (sd *Dispatcher) replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
func replaceDomain(metadata *C.Metadata, host string, overrideDest bool) {
|
||||
metadata.SniffHost = host
|
||||
if overrideDest {
|
||||
log.Debugln("[Sniffer] Sniff %s [%s]-->[%s] success, replace domain [%s]-->[%s]",
|
||||
@@ -145,8 +161,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 +174,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 +184,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,15 @@ import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/constant/sniffer"
|
||||
|
||||
"github.com/metacubex/quic-go/quicvarint"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
@@ -21,6 +26,16 @@ import (
|
||||
const (
|
||||
versionDraft29 uint32 = 0xff00001d
|
||||
version1 uint32 = 0x1
|
||||
|
||||
quicPacketTypeInitial = 0x00
|
||||
quicPacketType0RTT = 0x01
|
||||
|
||||
// Timeout before quic sniffer all packets
|
||||
quicWaitConn = time.Second * 3
|
||||
|
||||
// maxCryptoStreamOffset is the maximum offset allowed on any of the crypto streams.
|
||||
// This limits the size of the ClientHello and Certificates that can be received.
|
||||
maxCryptoStreamOffset = 16 * (1 << 10)
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -30,6 +45,9 @@ var (
|
||||
errNotQuicInitial = errors.New("not QUIC initial packet")
|
||||
)
|
||||
|
||||
var _ sniffer.Sniffer = (*QuicSniffer)(nil)
|
||||
var _ sniffer.MultiPacketSniffer = (*QuicSniffer)(nil)
|
||||
|
||||
type QuicSniffer struct {
|
||||
*BaseSniffer
|
||||
}
|
||||
@@ -44,67 +62,160 @@ func NewQuicSniffer(snifferConfig SnifferConfig) (*QuicSniffer, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) Protocol() string {
|
||||
func (sniffer *QuicSniffer) Protocol() string {
|
||||
return "quic"
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) SupportNetwork() C.NetWork {
|
||||
func (sniffer *QuicSniffer) SupportNetwork() C.NetWork {
|
||||
return C.UDP
|
||||
}
|
||||
|
||||
func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
func (sniffer *QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
return "", ErrorUnsupportedSniffer
|
||||
}
|
||||
|
||||
func (sniffer *QuicSniffer) WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender {
|
||||
return &quicPacketSender{
|
||||
sender: packetSender,
|
||||
chClose: make(chan struct{}),
|
||||
override: override,
|
||||
}
|
||||
}
|
||||
|
||||
var _ constant.PacketSender = (*quicPacketSender)(nil)
|
||||
|
||||
type quicPacketSender struct {
|
||||
lock sync.RWMutex
|
||||
ranges utils.IntRanges[uint64]
|
||||
buffer []byte
|
||||
result string
|
||||
override bool
|
||||
|
||||
sender constant.PacketSender
|
||||
|
||||
chClose chan struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Send will send PacketAdapter nonblocking
|
||||
// the implement must call UDPPacket.Drop() inside Send
|
||||
func (q *quicPacketSender) Send(current constant.PacketAdapter) {
|
||||
defer q.sender.Send(current)
|
||||
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
q.lock.RUnlock()
|
||||
return
|
||||
}
|
||||
q.lock.RUnlock()
|
||||
|
||||
err := q.readQuicData(current.Data())
|
||||
if err != nil {
|
||||
q.close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Process is a blocking loop to send PacketAdapter to PacketConn and update the WriteBackProxy
|
||||
func (q *quicPacketSender) Process(conn constant.PacketConn, proxy constant.WriteBackProxy) {
|
||||
q.sender.Process(conn, proxy)
|
||||
}
|
||||
|
||||
// ResolveUDP wait sniffer recv all fragments and update the domain
|
||||
func (q *quicPacketSender) ResolveUDP(data *constant.Metadata) error {
|
||||
select {
|
||||
case <-q.chClose:
|
||||
q.lock.RLock()
|
||||
replaceDomain(data, q.result, q.override)
|
||||
q.lock.RUnlock()
|
||||
break
|
||||
case <-time.After(quicWaitConn):
|
||||
q.close()
|
||||
}
|
||||
|
||||
return q.sender.ResolveUDP(data)
|
||||
}
|
||||
|
||||
// Close stop the Process loop
|
||||
func (q *quicPacketSender) Close() {
|
||||
q.sender.Close()
|
||||
q.close()
|
||||
}
|
||||
|
||||
func (q *quicPacketSender) close() {
|
||||
q.lock.Lock()
|
||||
q.closeLocked()
|
||||
q.lock.Unlock()
|
||||
}
|
||||
|
||||
func (q *quicPacketSender) closeLocked() {
|
||||
if !q.closed {
|
||||
close(q.chClose)
|
||||
q.closed = true
|
||||
if q.buffer != nil {
|
||||
_ = pool.Put(q.buffer)
|
||||
q.buffer = nil
|
||||
}
|
||||
q.ranges = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (q *quicPacketSender) readQuicData(b []byte) error {
|
||||
buffer := buf.As(b)
|
||||
typeByte, err := buffer.ReadByte()
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
isLongHeader := typeByte&0x80 > 0
|
||||
if !isLongHeader || typeByte&0x40 == 0 {
|
||||
return "", errNotQuicInitial
|
||||
return errNotQuicInitial
|
||||
}
|
||||
|
||||
vb, err := buffer.ReadBytes(4)
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
versionNumber := binary.BigEndian.Uint32(vb)
|
||||
|
||||
if versionNumber != 0 && typeByte&0x40 == 0 {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
} else if versionNumber != versionDraft29 && versionNumber != version1 {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
if (typeByte&0x30)>>4 != 0x0 {
|
||||
return "", errNotQuicInitial
|
||||
connIdLen, err := buffer.ReadByte()
|
||||
if err != nil || connIdLen == 0 {
|
||||
return errNotQuic
|
||||
}
|
||||
destConnID := make([]byte, int(connIdLen))
|
||||
if _, err := io.ReadFull(buffer, destConnID); err != nil {
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
var destConnID []byte
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return "", errNotQuic
|
||||
} else if destConnID, err = buffer.ReadBytes(int(l)); err != nil {
|
||||
return "", errNotQuic
|
||||
packetType := (typeByte & 0x30) >> 4
|
||||
if packetType != quicPacketTypeInitial {
|
||||
return nil
|
||||
}
|
||||
|
||||
if l, err := buffer.ReadByte(); err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
} else if _, err := buffer.ReadBytes(int(l)); err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
tokenLen, err := quicvarint.Read(buffer)
|
||||
if err != nil || tokenLen > uint64(len(b)) {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
if _, err = buffer.ReadBytes(int(tokenLen)); err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
packetLen, err := quicvarint.Read(buffer)
|
||||
if err != nil {
|
||||
return "", errNotQuic
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
hdrLen := len(b) - buffer.Len()
|
||||
@@ -120,7 +231,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
hpKey := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic hp", 16)
|
||||
block, err := aes.NewCipher(hpKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
cache := buf.NewPacket()
|
||||
@@ -130,6 +241,7 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
block.Encrypt(mask, b[hdrLen+4:hdrLen+4+16])
|
||||
firstByte := b[0]
|
||||
// Encrypt/decrypt first byte.
|
||||
|
||||
if isLongHeader {
|
||||
// Long header: 4 bits masked
|
||||
// High 4 bits are not protected.
|
||||
@@ -153,8 +265,8 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
packetNumber[i] ^= mask[1+i]
|
||||
}
|
||||
|
||||
if packetNumber[0] != 0 && packetNumber[0] != 1 {
|
||||
return "", errNotQuicInitial
|
||||
if int(packetLen)+hdrLen > len(b) || extHdrLen > len(b) {
|
||||
return errNotQuic
|
||||
}
|
||||
|
||||
data := b[extHdrLen : int(packetLen)+hdrLen]
|
||||
@@ -163,12 +275,13 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
iv := hkdfExpandLabel(crypto.SHA256, secret, []byte{}, "quic iv", 12)
|
||||
aesCipher, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
aead, err := cipher.NewGCM(aesCipher)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
// We only decrypt once, so we do not need to XOR it back.
|
||||
// https://github.com/quic-go/qtls-go1-20/blob/e132a0e6cb45e20ac0b705454849a11d09ba5a54/cipher_suites.go#L496
|
||||
for i, b := range packetNumber {
|
||||
@@ -177,13 +290,20 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
dst := cache.Extend(len(data))
|
||||
decrypted, err := aead.Open(dst[:0], iv, data, extHdr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return err
|
||||
}
|
||||
|
||||
buffer = buf.As(decrypted)
|
||||
|
||||
cryptoLen := uint(0)
|
||||
cryptoData := cache.Extend(buffer.Len())
|
||||
for i := 0; !buffer.IsEmpty(); i++ {
|
||||
q.lock.RLock()
|
||||
if q.closed {
|
||||
q.lock.RUnlock()
|
||||
// close() was called, just return
|
||||
return nil
|
||||
}
|
||||
q.lock.RUnlock()
|
||||
|
||||
frameType := byte(0x0) // Default to PADDING frame
|
||||
for frameType == 0x0 && !buffer.IsEmpty() {
|
||||
frameType, _ = buffer.ReadByte()
|
||||
@@ -193,79 +313,123 @@ func (quic QuicSniffer) SniffData(b []byte) (string, error) {
|
||||
case 0x01: // PING frame
|
||||
case 0x02, 0x03: // ACK frame
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Largest Acknowledged
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Delay
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
ackRangeCount, err := quicvarint.Read(buffer) // Field: ACK Range Count
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: First ACK Range
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
for i := 0; i < int(ackRangeCount); i++ { // Field: ACK Range
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> Gap
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ACK Range -> ACK Range Length
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
if frameType == 0x03 {
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT0 Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: ECN Counts -> ECT1 Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { //nolint:misspell // Field: ECN Counts -> ECT-CE Count
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
}
|
||||
case 0x06: // CRYPTO frame, we will use this frame
|
||||
offset, err := quicvarint.Read(buffer) // Field: Offset
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := quicvarint.Read(buffer) // Field: Length
|
||||
if err != nil || length > uint64(buffer.Len()) {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if cryptoLen < uint(offset+length) {
|
||||
cryptoLen = uint(offset + length)
|
||||
|
||||
end := offset + length
|
||||
if end > maxCryptoStreamOffset {
|
||||
return io.ErrShortBuffer
|
||||
}
|
||||
if _, err := buffer.Read(cryptoData[offset : offset+length]); err != nil { // Field: Crypto Data
|
||||
return "", io.ErrUnexpectedEOF
|
||||
|
||||
q.lock.Lock()
|
||||
if q.closed {
|
||||
q.lock.Unlock()
|
||||
// close() was called, just return
|
||||
return nil
|
||||
}
|
||||
if q.buffer == nil {
|
||||
q.buffer = pool.Get(maxCryptoStreamOffset)[:end]
|
||||
} else if end > uint64(len(q.buffer)) {
|
||||
q.buffer = q.buffer[:end]
|
||||
}
|
||||
target := q.buffer[offset:end]
|
||||
if _, err := buffer.Read(target); err != nil { // Field: Crypto Data
|
||||
q.lock.Unlock()
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
q.ranges = append(q.ranges, utils.NewRange(offset, end))
|
||||
q.ranges = q.ranges.Merge()
|
||||
q.lock.Unlock()
|
||||
case 0x1c: // CONNECTION_CLOSE frame, only 0x1c is permitted in initial packet
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Error Code
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err = quicvarint.Read(buffer); err != nil { // Field: Frame Type
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
length, err := quicvarint.Read(buffer) // Field: Reason Phrase Length
|
||||
if err != nil {
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if _, err := buffer.ReadBytes(int(length)); err != nil { // Field: Reason Phrase
|
||||
return "", io.ErrUnexpectedEOF
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
default:
|
||||
// Only above frame types are permitted in initial packet.
|
||||
// See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.2-8
|
||||
return "", errNotQuicInitial
|
||||
return errNotQuicInitial
|
||||
}
|
||||
}
|
||||
|
||||
domain, err := ReadClientHello(cryptoData[:cryptoLen])
|
||||
if err != nil {
|
||||
return "", err
|
||||
_ = q.tryAssemble()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *quicPacketSender) tryAssemble() error {
|
||||
q.lock.RLock()
|
||||
|
||||
if q.closed {
|
||||
q.lock.RUnlock()
|
||||
// close() was called, just return
|
||||
return nil
|
||||
}
|
||||
|
||||
return *domain, nil
|
||||
if len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(len(q.buffer)) {
|
||||
q.lock.RUnlock()
|
||||
return ErrNoClue
|
||||
}
|
||||
|
||||
domain, err := ReadClientHello(q.buffer)
|
||||
q.lock.RUnlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
q.lock.Lock()
|
||||
q.result = *domain
|
||||
q.closeLocked()
|
||||
q.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hkdfExpandLabel(hash crypto.Hash, secret, context []byte, label string, length int) []byte {
|
||||
|
||||
@@ -3,35 +3,184 @@ package sniffer
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type fakeSender struct {
|
||||
resultCh chan *constant.Metadata
|
||||
}
|
||||
|
||||
var _ constant.PacketSender = (*fakeSender)(nil)
|
||||
|
||||
func (e *fakeSender) Send(packet constant.PacketAdapter) {
|
||||
// Ensure that the wrapper's Send can correctly handle the situation where the packet is directly discarded.
|
||||
packet.Drop()
|
||||
}
|
||||
|
||||
func (e *fakeSender) Process(constant.PacketConn, constant.WriteBackProxy) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (e *fakeSender) ResolveUDP(metadata *constant.Metadata) error {
|
||||
e.resultCh <- metadata
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *fakeSender) Close() {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
type fakeUDPPacket struct {
|
||||
data []byte
|
||||
data2 []byte // backup
|
||||
}
|
||||
|
||||
func (s *fakeUDPPacket) InAddr() net.Addr {
|
||||
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
||||
}
|
||||
|
||||
func (s *fakeUDPPacket) LocalAddr() net.Addr {
|
||||
return net.UDPAddrFromAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
||||
}
|
||||
|
||||
func (s *fakeUDPPacket) Data() []byte {
|
||||
return s.data
|
||||
}
|
||||
|
||||
func (s *fakeUDPPacket) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
func (s *fakeUDPPacket) Drop() {
|
||||
for i := range s.data {
|
||||
if s.data[i] != s.data2[i] { // ensure input data not changed
|
||||
panic("data has been changed!")
|
||||
}
|
||||
s.data[i] = 0 // forcing data to become illegal
|
||||
}
|
||||
s.data = nil
|
||||
}
|
||||
|
||||
var _ constant.UDPPacket = (*fakeUDPPacket)(nil)
|
||||
|
||||
func asPacket(data string) constant.PacketAdapter {
|
||||
pktData, _ := hex.DecodeString(data)
|
||||
|
||||
meta := &constant.Metadata{}
|
||||
pkt := &fakeUDPPacket{data: pktData, data2: bytes.Clone(pktData)}
|
||||
pktAdp := constant.NewPacketAdapter(pkt, meta)
|
||||
|
||||
return pktAdp
|
||||
}
|
||||
|
||||
func testQuicSniffer(data []string, async bool) (string, error) {
|
||||
q, err := NewQuicSniffer(SnifferConfig{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resultCh := make(chan *constant.Metadata, 1)
|
||||
emptySender := &fakeSender{resultCh: resultCh}
|
||||
|
||||
sender := q.WrapperSender(emptySender, true)
|
||||
|
||||
go func() {
|
||||
meta := constant.Metadata{}
|
||||
err = sender.ResolveUDP(&meta)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
for _, d := range data {
|
||||
if async {
|
||||
go sender.Send(asPacket(d))
|
||||
} else {
|
||||
sender.Send(asPacket(d))
|
||||
}
|
||||
}
|
||||
|
||||
meta := <-resultCh
|
||||
return meta.SniffHost, nil
|
||||
}
|
||||
|
||||
func TestQuicHeaders(t *testing.T) {
|
||||
|
||||
cases := []struct {
|
||||
input string
|
||||
input []string
|
||||
domain string
|
||||
}{
|
||||
//Normal domain quic sniff
|
||||
{
|
||||
input: "cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b",
|
||||
input: []string{"cd0000000108f1fb7bcc78aa5e7203a8f86400421531fe825b19541876db6c55c38890cd73149d267a084afee6087304095417a3033df6a81bbb71d8512e7a3e16df1e277cae5df3182cb214b8fe982ba3fdffbaa9ffec474547d55945f0fddbeadfb0b5243890b2fa3da45169e2bd34ec04b2e29382f48d612b28432a559757504d158e9e505407a77dd34f4b60b8d3b555ee85aacd6648686802f4de25e7216b19e54c5f78e8a5963380c742d861306db4c16e4f7fc94957aa50b9578a0b61f1e406b2ad5f0cd3cd271c4d99476409797b0c3cb3efec256118912d4b7e4fd79d9cb9016b6e5eaa4f5e57b637b217755daf8968a4092bed0ed5413f5d04904b3a61e4064f9211b2629e5b52a89c7b19f37a713e41e27743ea6dfa736dfa1bb0a4b2bc8c8dc632c6ce963493a20c550e6fdb2475213665e9a85cfc394da9cec0cf41f0c8abed3fc83be5245b2b5aa5e825d29349f721d30774ef5bf965b540f3d8d98febe20956b1fc8fa047e10e7d2f921c9c6622389e02322e80621a1cf5264e245b7276966eb02932584e3f7038bd36aa908766ad3fb98344025dec18670d6db43a1c5daac00937fce7b7c7d61ff4e6efd01a2bdee0ee183108b926393df4f3d74bbcbb015f240e7e346b7d01c41111a401225ce3b095ab4623a5836169bf9599eeca79d1d2e9b2202b5960a09211e978058d6fc0484eff3e91ce4649a5e3ba15b906d334cf66e28d9ff575406e1ae1ac2febafd72870b6f5d58fc5fb949cb1f40feb7c1d9ce5e71b"},
|
||||
domain: "www.google.com",
|
||||
},
|
||||
{
|
||||
input: "c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738",
|
||||
input: []string{"c3000000011266f50524e8d0fe88cbf51e3ad71a13198235000044c82dc5d943fb34cc6d5c5e433610dc7a44f5951935c2c1d14ac641b02472340a892c4492dbfe3f8262109108fc36d96bdc1e9e46b5f1f6ef6104add2aafbfd8e79246eb3b4637541aaed7d195571724e642ab4d31c909f1db86e7d8516117ce8716bd1e3acb664c499086b0f3bc7258595420e7bb969f934457d195e832ffff4ffddf11123eeadacc48190e356c8f0f6abc381deb7e285e3b0613a795b19bddb9f002ffdf6fd70f0ff2072302b33d2421aac6540bb9f0e85c7237af0dd56225b2264d769160febab952e64bd5155f23e58c6113891143f946591032b41816aed3ac54f521f60605f86791de24c5765b664c1348cc53d5d631b4bbefe1915f2b21fefafb47badeb72d8ba1fd5c3cfeb0ba9d0112396f170e94cd33952c4fa87997b870931bf1a300e8e127f530815ff087815b4f9d004cbcd17013ac143847572a1655a5b36e054e8b9951d747c2c6ff25d7b2edb13a2a6b8074062332f2191f6830cf435a4ed9db5d9c4eb43a143bf3edf0c48f6f9435dafad4afb743a5a33990379df953ecd388e848aff0ebba9ccc052b8303c0bd1fee7e7553af1894e81b7772818bb69249540ccb8cfb47b1517abaf71c81c3bd271f1a5f1b66465f850f377c9db682b8e543c3d0c10fcd2dee263630889b7d1d521d1d27e866ea4ab5f43790d6a7f76ceefd5783678ca92cc131fa42fc4a01e2a81cad734ddf17a53e1bda8e0a21afc9e8c1118c9459b13519f5b3c3d9692c92234f01129d47ae8ec70625170847472801190b46d36f73b868f55f5a18a3cb05af6d38610e0829e4fbf13ddcc202341702e43dcf33be76ff4afe327e5783287c137aad075752940b41e7d9f5146e36d908897c6d7a9fdc343fde2d9c9d6e6a6b237669bd3e6abe0a732861a679eadfa29a876c6a646953c9361830811b012b26b31c9e7158f8de9c9a108346ddee3dd3886da6258364c1281bff8e055f6384e3a23e198b5e6b726fa7f811b3338072019d4b5fd05891770d11e3ed6ab5f7ed33db1c6220c5aa8fa1909949ac55d5435b75982e17aa80940fa574f0aba4dc340129cad491fdf1f5e05c4e83e36ad29ff38f15e1c9436c792024442f57f07583d671dd05446c84ea20b471303f6ae4e5e13f244d671e0ebe94d3d5c17d3f3f378cdd51fa8a6d2c977c78a2397dd1e251cd979803d617d45f575e5d9db0a28b3c4c25fe2af24af5bddac09786b6d6d8aa19cfbd5409bdbfed7d518ef5c863f3ee757bd9d37cddc546cc57d2e52b6ae58789f297a300f1d76c3842603eae4b1224de31a939a68875c86e697aeebf7ebc65568f43fc681bacab830ac4a2164d324e90067125bad702192d01cb3cb3d2689ae681967e86fd7ac93a25cf2e905c88ca5ad7d11962f021754cf3f61224517bd3411d5b5a83955bcea79d702466d073a6eaadc1202b3693e555b051a5b19457023a01e7f943742bb7f5f8aeba8d4e363973aebdccfb12479619cfb93e833be702a307e796dc7431a48abd9b755b392c510b98cd20ef778e2ac88d6a04f23ba8a253d7eb7c13e0c88c3a21f7e23857c58704d139703a47e0965bf2dc8810dc36894ac1f3da73c155e271c106a718b2d184e4e5637c820fe909984642960edfc9e62ac50af5dd3feee6bc560ced7bda676d4e290c9c5916fad52180bbc83d3483e95c79bac15c209936f21042dc2b6253eefdac06e7f4745044eaa0acedabf1d1c8cd9402738"},
|
||||
domain: "cloudflare-dns.com",
|
||||
},
|
||||
|
||||
// Fragmented quic sniff
|
||||
{
|
||||
input: []string{
|
||||
"c70000000108afb466a232f7f9f2000044d0168a15a021477ecb9731ed77784d42301462e2d59b0395adc1fa6b569d428583f100860d6b6ae29b6c1b8c0f9c0d9081475ff801f34a9e0677adf685f02b1169fe86c683fb51934915ff43921a73b98fb0b734406f8dd90ce6060d75e923b0d3c738291b421bf16de27ed4785d727ce589f5d0957c413c81d6ee75052e3ab50fe53f1abbb24a138a52e1412683992ad769e65ed301a736914843543e2a3e11eb395726d4fcc9283f8607b38685069f63d05ab8bf38aa24d4073a1e68fa1b6087cec44d7fa628342e9d88a0d20b381014cdd1a07b9d913a3bbcad0cfbddd0560617cf26054138075eb86e06db1e68781541587302e6dda86cae779f9848fcefcc33626f8953bfe4dc293d23e74c87020e79e9ffd58ee345382bd4d1d6e5a3389b0a977124708d05e3c305545857041734dc7092901ab54604b3750b3139dd3b8f2bd94cda89d85be3756fda6f0cfb6f66af3d2e36a7808ff7bce271a0272f8dbc88193ede31613433985cd35c7bd9b627d434e7b2e94b38402b8f1b5619a903572dcf4c2b864c6ee66657c9ec81e03fbe765037f83b2229171888ba08651fc78a1b50c7cc52f6dfe8273723e08932b1a16a6b717a80b5520cf3f40e46f9d9c350eaa914bf99dd4ab700cfdae21437daf695916d4f3121235e4913e0657d8cdcf4afd8f2c7ef977a2dfe49f46fef46c8fa6932e745311d4a6eb3124d5e0a204b9e3227e86a55e662f7002d4f4a72cba8c77c3adc3eff076dfb9195cf68455cecbbfc9b5444d9c4a4775bba68d57ff52edac6ce6ff4efbf6466579bf68308f2ba9a59b2c09506064091a86af621e9dae52366a90599db0d64a23944bc48966b6d3ab8e20f4afb5b0e94370d26a89a9c4207b454554e58ac74f62ffb3eb2686eaa596b9610322a5ce8eeb42f2ead1c71b11b51bc4f1800eb549a2bb529ca4a0d165ae461e45b556b2365e9459d531489d59d0dfa544a76c5c00b0a01270741d4061a331c32fd6cd0e68bbce49137b852e215c9db52f3e430416d8979520e5270be324f3d93132358c0eac35a4618ea7aad997dbbd8e99d4ea577271b935e3fe928f90abd94593806d272a565a414686b8e56c28e34b77671de6a696b09414380bc658c69a309d3225ba8493e9076dac776c845ce11a7ccd6cae58fba5434014250f3e211058b2efe3424b991d679a02ba949b086ba12144c7df3e049b5d026f386e4ae712c9b0b4b02730dd6862ed4e72730224cb6ec9101c5cbb7ee4fc30d497bb1dbf74ffdd49d8cae6c7c9a364ede453d9ae25edf27a2153ab285f3e3be66b2968d67a56480f1f74c4fe61dc69db3451f5b113d7ca02e5afa8627f579c07a9b1814853fb8fdaf0c0f220f89725c757f5617ba4e43cb4f3ad9ce18f48f23d10f9e8950b0fd737070655730532896d93df8768860ebf941365d0634db399feab1f8a88bad28d25e689c5a57321debb8d1435130e90a699e17fa5255f2063f09659a432e9ab5f89eeabe12756bfc5e02fcae2b78a9d0f570934b8d4af8f4afbd57549176f465a0cea485dd89c95a8ae915b4b99548a4c939710c16908f968368baf5f547cfee07f3cbb6142041d6e6084aac253a0d3aeac628cfe76f87b94c3806cb14a912ce8e4981e316511d5ede36f526805d6c3fab5b72d9d91f4eacd26e28cb181ec66611818f5c206ddd52488707a940dc12144ae825d25929bc32b718f46e471fdb30762d299b45c84f6310a72b60",
|
||||
"c20000000108afb466a232f7f9f2000044d00582e8683e329a63e5bf4dc93e93e325ff661e74b9cabefdfbf6065c7ab203c8a629534e87e5f2d4c0f463352904642358b8f137e99802c3a26cf22235782a777769ecd134c6b4d0dce6aa10b485c45ccdcf6deb805342e99ef97e2777aee0b2a44073843fccc2f8eb837031f76a8e968cb01c13c1268af095f54f860958e4062a84e2527bcc9b25a7791650a844de1b0c4b2476282a0e00c9de9d39a41914d1e797a88a8997b96b25a4c194762912b2ddee0e01a365f1afa1e82ea266c14ae94e47c90b5679e2cd00e63ee5a834505ca33463751bac22f3b87afc80099335dc7bfd12b7df224a23ced3d2e25b58a04c4b5cb089ca187abc54d782973c7bb157cc515c7508431ff5bdc227871da58b9ca8a9a576960f38edb384112b08e4c70672a6f23d17d9d901342e56c12370deaaafcb22810eb352f1a6d9377e96bdc1ad4dd397dbc6a227b70f204c1a4e9a4db2705763b82ec4df1fab11420aae547155c6b49abceeed997ff01b7d24e369c65f7edf18665d067c7d2bda5ec8623281fce8c77d893cb8a42053756713e910894a58ef5bf3d9f3a41071026660dd7cd05e1640767ec68f78e22c1716700ca9c0f076f90a65cffc394c10a32071c6532d07b59414181070d08c9c84e3d13842718d51bf90dd36ab1b3f708df7eeb3939dc8553787308983c3e9ba971e7d447788477a7140196c2f717b9ba4f5da92d73316dd11c1d1830b4200f26f733a6c65ec1cc21549b485e3a43dc7a2b68e95466a53544082a20d9a43387a7ccbfd353f7e590b7047f13bfc0d91923c2d75dad4f8091ea96502f98e83e5c30e52e4cd5c670f6c2248ce37cd6ee8b3970531fbf0c53c5fa9a0d73200442b755c91fa4f70524ffe8a36063b6709d3aa9f6b53eb0aaecc57a8c8c9a7ac5e57e03e9cfb290b67dd8222a245ff5439914147e2799fd1cd2ca2cb22fda299443b81e8024adc59d098058432fa4bde376b8e59075f6b86427b4ef6cd7c83b5c08add0c3d3543aee8d672c41cb287c1f0a17f1bc30f62a57490afb2d9f401bf302fd473ddbaf63f6883221579743d6aa1f386b8b2f5db06d7d6c36be81f29fafd14b82e863d744f116ce2be4921631f1fb2797289fffa9ee16a3e537ddfa52350546bc544459c0c9d66fdcbd41612cfc0e2744f50927983a3224291c1ae51608fbc00f40c60ec72573a7e128c3415b0d9a7db52de8ff763dd66e2eeb03ef2e67838c9e68cfddae4b86a3f34a69e0a473b5a73ab627282648df7912c11a4bf033ade185a8f438036b99b960aa6213c800abbbd751248a7ae600357ab888433125d49c5643705ecb8c86f2980050edd7e3c579ad6fcae9bbe2c8d8b38004426f35eadb543a3bef42355acb1b94c21d7eae7b6ed422ca0d58fa03b227b035628871465ed6509254c8a3bf43dfadbb247ecbc52d80d65e9c03c4bc7bc35a829502bde3868af9c33737cd88d70f7427790313eed4ed1938955c5dd360212ef700f274efcc8c26ea94c4e2e0937d475c5c4909edfb66714d15d12e153e5586725ce0c47e8a1506bb197366754ca8960508f22fe7b83a5eaa40f05f3cb87464dc6b848080c0e0cecf2dae82bfa42cc6f52694478dc3d00ab0e1ed696b98e26c7fd34d2efd969f83e284c28ce3f27b178f4691c772011f61722266153142dd0d526393e6c6848d201115b256e65f12b911a983bc2f96a5b4b99f63f0b58485a521553a3e1d4498ac5d4ee70c3f9",
|
||||
},
|
||||
domain: "quic.nginx.org",
|
||||
},
|
||||
{
|
||||
input: []string{
|
||||
"c00000000108e63b9140d034563d000044d066e1913892ec1d84c179dfa9596e0ce930171a134a09446a888d9e579a6f7bd77df6deda715b028d64f7866603c6deb468d60ecc6488b5e5ee2e2daa1840b76ead998023593c9ebc4178ec89cb198d3c79a867e27177a74ee5f3db74ea194e36e328047ffc3890192665a6feba09ba1e224967fa9575dc7b094e1c29c7f3be9961ba62e3e063f674a09786b7611138e1edaee32cd1d47839e840a74f25ed786463fc48bf3d38a4c793178ab7cbf5a3eb974415b9f9ef7861dfc73460594332f5545c7b7037043afdfc1aa62ac3dfb76ec2c6ae8ebd351f7483992c762d6483b3e2c1454c8ed939ce43f858ccca22d9149cc9da16af86a010be7f3248cf19fa442e94d625ec7f7144b01ac9afb8fb8c595d4cd12fcd2b2d9986371ae65f6f216bed152b79d2782d60f1f01e06b359f88900c4bb3f987f3ce336854a5beaaa616813af4e5f9bd82dca0af6886b544fff0261807bbd8cf90213299f5802b98edc27a6606be8e2bbc18fa7519eac260dcda139f164796a082908459c31aa964a5d3f6fed8944ad61bda126991468f3b7627f2470179619864f234a395ea3bd4f7ba4c0cdf9f5f0dd95d7d59476f2d2a36521c13886265a2fbbd4345e8d1d1e7b5d01a58fb11de23730b087e2b702200155a1ebd50db5751d279438822ac158173533140998a3056893bf470ac84720cb37a4a3205fa88267abc56520bcddacee06011d929c3a114314822d8ccf7cfef89f2fcf0a4fef800afbfca4a62ee848f22066f68c7d3c5c9a24402d422fc2fd5da6d3b470b0ea253f12a883705f7f78bd67006ade4f1c8a3e8fa052656b5b40dacd8062228871cc3bfb1a9c38472b0a720c3c750430edbcbdcecd46b144dfcaa009fee06770238d0270e80671e8ee5f5df18b86dfe8df2f121245c0710ccaefecbeda0ba3db945c768624dc38f21a4ac53741f4e58a5052f3d667fc466b69905f05d0843cfcb830163fae18dd1eb0ce62a59420db9c44958a0eca9ba4258c8060a9956343f155da6c55b2060427d07d9e311729d2971439c7541ae2babfce25a3f5f361fde86c39ef6c04e4e3cf7dd70c9cc0758ec5db3f0cb368e2447080af51c8a5fa6b84ec3175d2d3e6d877b6953e433b4e94b52e1a5f2a1ca37124c27e47f9de5d4c74644181cd37f3f3863ca529c0847bba91c246dadba94b4566b08eaa06a0db4d58b8cb0c8d3070533306a3089891b24a7c4e11b3aa50d5628fc1d136388e8bfbc420a6f12701333ccdc95dec25d09ce25fa4b654260965b91f05b1542c2ee02008d01de4419f14d6749c4bcfcc45a332ba0772def720ea3c8d207802418137b733e779eb406dace0b4b5f5e5e14c787f3e044e6d8160f90fc3c65bcc7f3449205b63294fbc11e9bb92c007d1cb59183eafbf76be9680224cb442806500d71870777d087bf864890848f4a79424c02304f2a6ee2b07f9257f4a2f185ee21239625e246cf680e74b85d292cca44261c6cee6da39bfac3882d28fe547a500f79519ffcd3f54ff5a905c99f22a5e8142c903c41adbe1eb9770b6cf554688529091b126ed2168a23bb191c2b89728e31773623bc58bcb9baebc2c664c79d6ffee7e4404e039723eb05e7f7835c87212431a0131603fcc3fe090cc2fda8239b8f42188b35f98d7fff949b3044544b3bb962ae236a664d76d0c751d9c9ed1271715d240f111febdf7045502f2afd7de8aaaac650511e7bc7716a5b6622ae925abb7",
|
||||
"c90000000108e63b9140d034563d000044d00b2498988864d8b7f59a00d26165f5ae638fc9b1c12d546ffd86212ccd85f654259cb8b8c9d753c696ddad7ee4847bf3b3c10063606cf3972f75e17ae23e73b6a3029f23541f674256d19677665cdd0b8ac15c3f60984bc14ff5dc7a9ae37395516204f2020965713fccaf35cb0a5823085cd6211d681dc6b39be9db46cbfef154a2b9049ed202e9088961b0b710e94bd73259b0967e4d6b8cdfd5b72774fee2f2ceb16bcafa010f247c43b0a9ca25578e7d45bfda7edb82e91f8e1c0a2cfa990223bf97ece42862d3f329521fe2d12493b717f174f966d173102e5cca10943d5b612101d65d0dd48b44416f9ac1eac4575558ecaaa39c47ade2dee6e25fd219d799b499143b47a5bf449701b939c1dde111349cd0d63efd2ff74fbd3573ed40abfdb2310e2740da40fc50c7a137a3f32c3a26b3d407f80e669fe7f9a3542fdd412a9cb53f845d9c1af0814377bf92e30f05ee387fb8675807a6de083c85d3d7860601c8170923c53e5773ee388b68e510a28cd7009c485bd4cb861eddfdd265de042e5a018d20cb810614e2bb17b0f52d6bf620a6f173e0b41951e1b83ffb29e3b3b3c5d9fff13acd3b409021195201d003e281d8cda7b0f02c273e17b1f9b9e8cec4296d65a1c4923b78a2e4273cb42e4e159980472e440078e542eeddcc5a9bfefa5a72871fbcd9ebb74fef20a50215bf75cfd8572d5ab9ac5945e8d6ca35884caf0af0446ee9aab0a1cc3a452ec79c9de786119e63bb3a75fce0ae29c15a0c320fff87e87cc23a05e75b4f4b30b75c6aa036c4b6657f8200ea014185b31ee7fcd00d1eaf40973f347fae227f89d41794fa57ac1ed1efda3ba840ef27852cf33a9dc9e2d77b56af9ced9e75707837aa8c5395cdc15134ba132de87152ce53d506c53284dab912bbc276542504cc94afaca71a5173ff13ea6cb45b47dde9965428ba5d8eb968cc2a5729c2f9b8f1c1de208943a2cd565196e040dcc415d769ceb6300c7909d7e32bbbe83c4cbf4d49f6e34fe56b651838628f3a0001e99f39cafe45c98e455aff8d98f89942a862f7505b9f7fe3f64dacf8c574affacf91c2c05f094127acaa5187f9dfa188f67db421243a02e583942138c2edf45fec4c6b6a8a791da9055be247e9b252e9f7c1330e76f9cb3aa5feebb21f871315b5fb90a1df0b8056513b74daeb6ac995f85c64150ad115a14830d145e5f4e6638c26987b676a1dd19a9775df29ab442ce6143b0fbf8f8d4618084896e34812ed59d63041e2b4ccf6c959a6c849813dd926082bb7b1adedf69246547f335552bcdbae7e466ac31e07e442530ad114abebc6f58015b786e7f35644307fa7ad3d9248c56c8ff472735c6911da1843fe53821b8f5180f8844db4a9f7a826a919fd93c4db4d25861054929260dcdc46d085827c46d60f1097424a6ef250f5aaf3235c80230eda4eb580ce93e1ac8aac422a7aa1241562af601981b84b74949f1c476705c8030eb5d447b2414f9716ff3fd606cd750030b94345c016078bdcb97b7ebc24f661fbd08802f32df18d6a2aa85bfe2e9b8dc76b121c44ae9f29e4413051b527e99fde29720724337476c0eff325cb6220a290a9eb852151c84836729d6a223032e2c638857d9e7f469b84d7d650c45e56e763aee73f902e82b055425c4568725e2d4efd7fde8b02906bda48af86bf47ea27ff00f4528494b74be9bbff001cc841449a184a4e00d64e51a72660a2c21f704f",
|
||||
},
|
||||
domain: "chat.openai.com",
|
||||
},
|
||||
// Fragmented quic and 0-rtt packet sniff
|
||||
{
|
||||
input: []string{
|
||||
"de0000000108c2751a596bd51c6e004041948ab7d9d493e9e1e9902a7734534fb9eaddc70ca7f821d1b58a406b23ba9db1d03266ae74765b03fac21c284fd50cb0a3d1ca71d8c3cabef5553dd1cb748ac662",
|
||||
"c50000000108c2751a596bd51c6e000044d0538af4ba75e226a6fc7f43e7f1f59610973b8a6670bb8338ca7ef7d90f81aa59f179dae5f8f6dbd24ec6fe576b28f6ce6cd46f26de143b8c99cdadaecf2041948a61bd5a8591486e10022fd100aa20e6423b4f4ca5773edb1aba79b73d6150ee185e66da60e658b2a698098462122b6b80c7fbc5542b0b8e9532898c1f31aa2ef55cbdf036d74c3069abbb261660f048d950b00b7db279ec2bc39912102679ddbffb53f1b1921f137fce43e164af86c72908532f4cdc48eb462a9d9e9cdd6d3c3faaf8aa8aea312dcac5d6aa75b1ade4af6901576649da7e3efd4199b92107d7acee8bbf06734b2484957c3d8cbb1f3fc0ccd56c55223628ed8ea514ffd101bac370c97b28c7da81175ab0508c0002d458cf41f7159dfce22b447c1ec502c186b782c1854718b7fc0fc39e5c09aee31113fc4c5003803fc27ca48850c08a54dbbfdef6ea9a6a138cac0ecd045cfd5607cb6c99c39c0cb21778857f97416b78fa7c6ac8ae3fa2ef2adb3b85fe3fdba70ef9265bb3d54e56ec68b8887d54d02d4a571a6b793ae4df8ff171c881a554b5c5a7848351d446ab94c90ee9c600f03b785fee6300450a4ffc2a55d417952e15449a491296d463ac6942bce4ca93c99440396bc8984073ec028b11ad412e97e26f9248031dc4b1a6ae385803bb578fa1a3b3a58a8ef19c6c511f17b28a275e8c40e51fca8f410a4a1879b5d8749a44a6a9f97c0c9df25318cc28fd0cc61eea78ddc603a17e74eb542c8c08cdbaafa3b44566db4d67e8d1429332375cd30cdaead9594c46d8ce91bce9813c3ca23f55ec2f4dd3ff141471bc3df590367bc65e4830018ff7d845ec4987d11e471d114c48acd1ae9b7670341a34077ae59ea6c3bfc4675cf419d37db48a98a5573b69867039731f537098b46415a193f50b2c85bf9e5da45d6757c5c366e21f04ea62d64b81c28be5148d89e53535414067cf609e59686b7fd135f5cb473e57f6c82dbb291308a1065e0f755935d77517adecee55e72cf37ecaab1b5c0c6e0c7463a014e7e439757913f6e43abb6af775d21ab6e43cbdbcd1935a000cf8025ebc11378d86d6f72d51bf2dfe4be1db5d3b0fcacd13e1b9fbaac6e9153c3d1f4e876f2fa9c3cfc84fd0910b778105b66be70827b1830b7b3c9633af5d83ad527efd81498cbbdd112873cc5ced573e6579acfe817b62280c2122b582b591d52b96cac047bff91192a5cfc001d15c811e055dcb1c9710dc892258ed1ab5152af2cfc57a0b93205dd41fd82b86090b4281b1493a8828ebc96bbd603b888cbca4a15799a5f3eaef93655d5609948080ca57c696d0ffc9a07665bdb063b547bb5a862c3b058c9efb2e7b79cf405fd83efacaa4b8e3a1fd126270587119756562c03d69a9cb67550369030a0204e531cb8df91ab2dfa2e4106c590c59b1b13c447843937929a574d3ea1785db0d52b4b2eeefd1a07c69729bec7c2813c9eb1249f706b3cc14a3d489d6b42a641dfd9e91aa70c7d3222e154af2d7fc1a8f48e5ba11739ae128d1f32ff929aaf4b249df5ea23f7847301e36ffda02342cdf1bd9dfd1979cbd8de32eb8b1eb8c415ddd267efe53f54678d9fc32435b34b00ee2256d8b6190e30a280df5bc48cf9fd669a52469954deccb0f1da37371d513ea57f31ead22a34f9379c7931fd18286d9fde6ecfaac8ca2a9be79d688c5401c65407543c066532f6621f256551c4a98a86b543c576ed0f3254daa4915",
|
||||
"cc0000000108c2751a596bd51c6e000044d07b624bf3d95fb3b7299b67dd836fbbbeb05a51650f9b2da3b2695070a0d19ab0d5334cc04de7ea7494fbe6c438f4e84fa56a3f246132468b5b4f1ba0fcc0251cf278338e15fdd715d5bfed18c1f98ca3cd3cc7b6f904aeeea2914a8b998dd3ae7df694c49c1742dccbf4c3472ddfe2e447959655459c11f18bddd9481eb597b887fb3f90a7d0f05224a144f87a5fdad502ea1e46c1c9f4b4154bafa4542c026296040228703bcd020202acceb772b596bf788341cceca864c8907037c39739e511b04e8ba956efa0fb5cb151ac90eb5817444f6488d593325ad4466058ba45214b965c5738f33d5591624584559ba18e89913b868619d498072e3aa1f333f5d6e3d1db88b28adf7d9350c3c383c1eda894f36bf1bb2a58c7a5e5c8b20597b71a099e46bbd3d8894877e43b0183919185b4e9f059472203979d3334c535fc4eaedebebc79bd1e423184765047a50e6dcc76ba2b23ad23511cae2edd2ad8e7f7f302226dbf6c0e4dbc8c08cda26340b9abfef1ef3333cd511295f14c87197d7890576b4076dd9686047854e67733599d96a99194aecf7b927cae2e5fa4568afc71e748dabd3bd71e6c3984f45b06a068a7c9c3a1ca7b5c245a9bb2cc7e2726e833e283430a25b6ccad55bc5b7644b44f99fedeff3c3bbf995a0387cf1e45a5684e5d1c01350d0cd2d615ffb6d1011d80ad16b75925efcbee483e4e2c0e2386e9e1b35b5a107ed97058adb60e323342989559856faeaafe5149bdcd60c113230f9923b2f654c95f986944a014198686f9c2275053c05080e3bf9fab7d46302948b152e2f2fb1ecbe71b412016b3f25ae512ad45cd096d5f284a0c2808b5eab03b4b9b2dff4d81bf234e75e30d480f39a5f9737563e31a19b14d1038296915af33e0ac0dc18e9c871e539e8772d525e5fa19afc582b1c00ae573af39fe293e16d182bbe57af5bee1c0939862ffb62e3d52a60aeb71e4db2a4a1708e75afa5f37d72cd6c0e036abcb4eb8db6515fbbdf98be95d0a6d261a9445797a8f38c3579a2f04c9f5b74dfd1ffaba2c6aa05959704b9b8cb0db30bcc360711c5afef0d1e7c2b076466dcaab104c70f3cc0cda33d7a47462c3fa3d7e34a99b2d8ff3fe5cafd27ede28b9e09b547cf955b97b0d0d4ec126957601c6982d176252be422df3366118895ca25fe27a96c9c234d484fe98634fb9e970e0d2b096f2ced5d56603505990a65363726c828aed2df0f112e0c44f058424ff5c25ae60aa2cb5fdfa289e8ebb63908365aa4e4609eae87e567f1e86d92c43992e6d505f55226fe3533f9fc9c9facff9dae02a3e3c97ca54191bebab93881c0e89b9de5bb4acd5c6fed5b1e7978803f693bfdbc125b4d08fd34fdc6aaf02444c4b06010b0eb2f15d86850a7aa5af05af438f6b7345fad4315f631bc5b017c7482e7af725a09844472f48e4de79b15284932a7e99a46ae72b187ee3faaa0f31a36726056e86eb706bc8eac04b68a3302307a157c91639f30bafc2d180670625673310a9a45a171063011e59c57c8eb67353a8ea344a87853e7b600c2b49a7a1b60a2904c0ea55951af6430667ecdfa6e90a8d2d0ed9857ba5b876cf78af190d5013d16208d2b30d02cf2c23e6ad1466f76c30d11034d5d2eca113e2764b2fb6298fc4940c16d971e28e3e6e5d0e8eea1ccb9b4b89741ed675861fc3680457ee08547f4efcf68bb6247313f8218ae3ec372e51ba8786ecae115dcff241e0",
|
||||
},
|
||||
domain: "fonts.gstatic.com",
|
||||
},
|
||||
// Test sniffer packet out of order
|
||||
{
|
||||
input: []string{
|
||||
"c50000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a448875483ec1f071622121a49c456dc3ce16bae5f61f84ceaef9e8b71db56479845b764507dd9416e8c44b8c93406a230945eb8e484471c1b6207c9afd944fa0fee555a5c966f27ccffb4bfed37fe3936f2c84e9852c0d46c7e2e94b897fcef18c4b0b83d966aef75c0af4240325a24668bc017e0d3f69680ea5b2f59bd0b964062bc40190be86aef3ed0716a18a67057f309faaf3a040222812142a399deb72ebb330d03d59961e2ca10cf78d40886dd094368a881db261068920968f6adf7a7b1266faf8842e71840a29859e877c66e3ebc47d7fe3ee586b6512d9b0e1bea82b302647706473e68dc8209f4e9ca19f1dd25fe386e62c21d9c741e75cb8b11606739ba3de6d6325ee3a9cd1bb2b9613746140ccdaaf936eefdaa1ca7ad73d684e5d82b1ba1dd3356ca0c881f6eee72c02c8b78d02a8217a8fd972e463c77374d0fcbb761459e3ab0bb5492e516d7d4304c19c16a4bed11ea7f4e75616a26a7c81b04ffa580cce04d59825b8ed929578f9219e64bdbc6352ae6e4150a993fc3cc27ce4d66c62893866b9053bb737ac40364094b53d91e8b325b9dab5f537af04f10bf8db644897b0b03b42b1bd6c3aedfe018a6e4f6533183649f4ef6a6300383430f86e802fb4e51976d056a3c40c3b53c847b8308cfbe54dc2d20b8cdc870c73f5fc22c376c35d9a85348ca6a2288ae03dda6b97f0f502f35219e19cff3a810143289cb1f0715f8785028f887bf02c656c9cc372bdc419290f05957ad3dee82b56db352db65aca58e6fa0bd2f753160dd9e7214968c0496be1ab49f978a9252e49266939fedf542760abd653dd38b1659bcf452c753cb89e8235bcf732afcff8f524331be9b6f4a5081c81255e68c358b3444fb1d57bf5659d86b6674544fe2826ca81ee52f93a17b3291826678e488c3074c259223845e4083a413af7fc93d9992823620a8d29d321438a760293e36c4232216207060dd3ee5c4036250ede71ca9cbe335a1e068eb3ff6c10a7f1c8204750d6d0f3145014949a7b4e88a723566ee5446f960a95d9f81cc45155443da561d85a3a311df8172a1c4eb118bf27ec4b3cc4573b1ab421d96d41cc1e5557797ca68f701fe75c474527144d30b9bb00a117637f88896b0b2dcb9bb29ba144ec384b5a085e82e7387e0560a4621423c306b041ad42e84928ce23bc2a7f995ef5c21616de43be8a1657847489b32c8e364846389e7c8cae99530c499f3662a2ae7090e54958ba940b5d3eaf1333ebcecc7f06f29f68ffd97defe65017519c29d355ecb0a4b47ab08dbad8cb0cc5c86de65dfa703110c60a0c55281925018fb4ef49fe5d0132dcc86602c2ab9921a8f3451480d3e931f01c2f9a81873435bb83860128aa78dcc950fb13e416d90ea969aa92763f9caefa0fe3ef4ea82e3af4a3e717fabcc589fe8cb9bfba6810ddf7def8c1445fc0048fb07be043a628e9c920bb72c04d3b9472caafc6c14bffb854a1ba2170dda919322a6d79eab92e3a88888a224093946b87840033fe41941f780f569eaf1fdac55e36b74514d72d09823d71f48f5d5f0ceb7b6d69c5da0e0408c1b13c265d4775db6a0f952ae72bd5c277b22c4be2f2728451ce31e921c856000d20da0489103bad6a6ab4",
|
||||
"c60000000108b4b6d5c8b9a19769004047007e07df0d887979774085206f2e7f0146b02a8699715a54fc71ef27ab5a9e8cfbf155497bed9e25934aa74db1b3b270112472b7bf7587423b3ab2aaf99de34cdc591bfe04cc0a44884e9e716461869ca408431e1ba92740c598aa74e9cd45706f28942f9cc64dbfd7c292cd33e82b50ae0e2e08dc478c19886718cde33e56c38517f8834d64904bf4fb1d30650caedecb9567ea8ef50157c287a2741e98a00f8e7e19e76bbb0143ac7862a49393f17ec66aa0e2c02123ffb5abcc96ccf92cd542c8f571bd7a4382ff81432d11f83796959696c38f2029db6c6a536a9ea24b74c848b95882562d74739ac95f5a069d48e8756d1a9750c7ebc23d4ee22d617b29b415b7458b3bb8106c22de3a9ace9ec689e6e00471aa33e570f7481d15911d7cf46a429cee1a416558c5e78360795d905ff1e0c81d18fcf4954131fa5b9289ed2291e122cdffd666c66209aa2cab01730739249ce293b3ba3abb31683c108bdfd51f54593f47411077e948f01105bd9bfff1578d235674e96a8b9cfdde119edaa960b84e70fd681312514151de1d5939c79abdfe4953e22be5ad3e6e242d0ce9b3f2e589ce3c768f610d4d3a32e33225d8a5ce2ad74a9b40859cdd9ea99f14fa2a7018e4b6aa6e46a0d73d46d161ec5d3b30bd55078e23987865551a605a33472931428ce222040d20c07d1ebe970e576d9d54ae688a3fe9388adda3da4d011a7cbb604f1f19d2ef1be7ef4713bfa84d4d69ffa606a08b61a1ebb99aacc4e19d0c5034642da1ce2d7d5abecc8adbbc6d7f72ff2da4ce5228ff8626509b38e17b31717c0b7821558b021ba81502d54da7e778d4526367109333383e7c67d5d5bde86bd4001fa13a703ff9259e1c2268ca8f4ed2e6c022a7466e2178bc725f59792803ba28c629e3df7696c416dc294b510920077b2d2b258fdc3506c36c42d37796c8fdb20ba797ee68fdc410325a355f6c1189aa9fc9ee220d42186677e3955cd3c844ce505cd601f04201cc390e923db2ea6407fa2fb4ca7f3f82d0a82d52697ff5ba5d4633bb0d655d7ee3348b89c9cb42870cdfe7c0c162babab4208a9a54700c5785d4134e9e33361480e3512ac8b556e11775536e90ee1270a4cb4d6bf2faa72d7e1f23ceb4fc3aded0e423b6be6a55bc25e5a99163b4f5f72ec4a24fe96f68c739d1848c92c4236a5a637d19871456b8dae671ea6ae5c16ed4fc257612a0821e6dc1cbe2ef4963a1436925dcc4e6ce528fa75e41f7721b379fae8ca09e6fb51d0c3e3ae6c19b98860ab9f74013146c6d375656dd1f530abfa64670a510390e9a54bb9a4ad19977491377c8cd743597bc156ee3f58cfcafa5a547b20852749e66fa8838c100ebde039ea25c8ec32b0c6325b793797546a095e79b9388d8e67dc6b4b3892f93ecd13e64ba4b2ad26fc810fedc374b831921531344c581927da9ba822bd625584d98c7582759ae40f01e14277a0a13d30c2c12536df698330d8aa6a3613a42c493c42692b468b4a2cc6bb6dd45684ee6115848110bf517074efd93bf212c071013f4359f140cfed17bbe10328f2026cb8ada16427122d3fc8a933119a1e3e4cfe2b95cbc73af5044cb099cf34247228972495488ebaa4696280d17665c421be5f1727c5d5b013d8aac0e9943bbbb7fbc2162a4000a306dffe3bc4425cf272f1ebb63c8e4998f867fa6b05d71a8642e29392244d4e2e2351bc149d665efe1b9519cb1b15005393f938d",
|
||||
},
|
||||
domain: "ogads-pa.clients6.google.com",
|
||||
},
|
||||
{
|
||||
input: []string{
|
||||
"cf0000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f9944884c88b10f198e849dddbc1e9f9bac61f98f67f27d5452da6e2bda1f5210a145b1f1416ad2fc15e60aa00444362630650bcd0ee47999b689a40100dcacf40a4c3d74fa6293d4a5cb0487d8c76787c04dd2b47ec7718df5a2dc6942069062617b3d40a95360802957419433436c9065bda5a6156291d909a079b6d3819941368d7e17a2e97e36be829bb421b44545af47e37d7815ee1f200ca28ffd361d955ebe0484fb234a7e8a7c68ad824fd14d517fa7b35f878beebaf3dd22bd9f7a39cb7e0fd8369cdd28c05a06323be7af0b2d69ed2a2f4ea9f25d000de71bf5bd6765a20ddf81d976cff2321f1a4584ad6c4b7e9a42a6d4aa3a02b59f7d994a8e4a3070a4646e51fdf354448420ebfd0aa9118d010d019cc168f2fe5a9ff0c42e6091676be11f28a372ea97d008a1a02efd58149106cfdec7ef86f5416c4b1a408d8efba6c8d4742d781374ff0a1a8ac183bffa1345dc8e3a7cce04f66cc865f434decb912dc9e8e811eb59b80d3e39d5788639ae7c5ede73a935edb47d907725656be0522195bd2c099b0241f36664fad1543e4ae43862252662707fb424a8f5f9486b8e3779ac24bac457671ad664475d1fc9eb1de3c46f624b559742b3477953552e44f20cc1725a11ab915423fdce7cfbc8dafebc0c43d1ac3d3373ca2f0210924433c46e5fcface47a65579efaa1999d52b2632f69c33c3c63537c01be68fb679f9229f8f68c5caaa23dc4c61d3c45dee90affed984dbbfb06b2659447400b4dcbf6e574719e8d49fe0dacea9509182a42f6463138d8693a3b8d797d3bb6b0b02648829d666341373939ac41a57e90fdc2469623b6e2d772199d7c806d5998f439603c0de8413f9d29f79323ec5410b409ab8c95547ab50bb921fe0c407b7aaaf663389bdea5ba56c023dc4622d6dd9cacd8f318a6a0297d041cc6ed455d906be50dc85a25ecb32f4a565432fec9f359833be1c6a6b7b4bd119d3c4b29932eeec8d140dd467ab4d969bd23e9d2a95b92835587f32428f957b6785b8206a4834e00a3013e0b6a5855f16207268bbdf311572c54d2e6ff9c659cd02c258f494c3b168ea170c69138b63e0dde487b72576e87657befa44548b0b4e1e5a837dbbe66a559cd1df8f2151ba513930243fd2b7705bd29b183dff966224d87ffabb74017d634ab2e4b368052504a7f6bc1c62d39a29dc2dcfba683bee2039e376ff391abbd13a0b89512fd8f6a4e66051dfa04e0e1a3cb4bd56a9b17e27651873bf2ed50f65cf1cc608afaf06fe7e6238347adb66f01d1f0b9b51f0078615553cb8ff8d6786b87e19dbc44000025693c4b34cfd695601a680efdc1e7465a981b0f028cbd3dbb938789f240e39223290e34ec303ff5c78a4a637ac04dad60d744f82e96c3c9e8ed6cb0248ac73b5b3a92007edfc1277c3cc6fa1d0045c1c371820f06bedaf046dd999665cc4745ddf8934084ae02e9238acae6dea330b5798e046138f5b15011875eae72d6eb6689e56e0ac5c5d9e25dc4fc1874cf37265e68ce5b8630b84ad8dab7704474f0bfd08ac295b3a508284fb6ff201f0aee6388d0e1d5cdaaf4c20429874792109f5b8e2f3eae6c397e46a510ed829a6746e523481465f64be4e145c83d6fa6951229d3",
|
||||
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f994488828e6082edd53e228164d8862067483762ea9523c90d565b9e4b185b7805eaf8220664264e82a95164ab6cab4fb3f5e795e246e7205aca236b3c94dde0ff4fa66ce0924d654829d59c3eb690470b20c5011c739102257e9c2247dee67c0b98190d0015154d31041aadb026b8d3a828c861a15dccdab0cec8cc99b8d6c2acddbb93ab66253e87ac39016507dba42e8fa9f5d22c7f27645a02361842a59ebb2eafefd0f3b92bd9692a96b93875defcfe2796243be8861c59ce5ab03f1d65d308ae456cb9656da1f01026ef0807cc9930021b29d69b36881c3e7d70fc68799ca81922008db93c9ca4a365ee191e214d9829481fd430194ad4583a0ab2e920c25244d7d64662872b3b69ab413ccf0dfb6bf2ea9a9b93e04ed19f8a0e146613ca9d511179f80aeab40d573590d38a7c10840e3f8b9ac1bd23b0826aecabf6d1cdb2aef02deb982c2029dd6d8bc21da6c262c8116b7b383ce8c9eec69da3e16c044dd96ae08a98595d128e89dd55e6dc8eb08b8d51327278027137f60a0e1b42878f98ca898587474f6d509c3a58ff4dd7f8b10905c200cf3170bdec725ee14a1ac8ebd1022509d3e499f5e72168eac43264d7246daa0bfc81a216ca97730b7e043cad8d8a9af5c443a5d15e9a88d82b6750c740eecfd63561712a185c69532b1a18b23513d7cf871f14d164ec544f22b6a8cc77d6fae5fc6e47eb64f08617098d229da78a378d6a0864684a7978f650c7922c907f97b0ebf2be29cc834ffe995c9636b310f4a8c2c5623c3b7b533518193d226923f111da1a0e8055b9053ad7f7504d194fbc3ae2b41cef30aa099624d5e229ffb56d5883a5a09163d22455cac52e37ee0ed5367b7c3bbcd4818a46b9b363b592c53c780eeae2c8b80a1d60d296614c998a9774f76453a58bc55d1c26bb10dc321c159858d7ba2f7855ba01aadf3585632c097e5471591dcc24d87e9b76509c10e2710310e4869de710ce0f484d326be751f8e9f765a685312423f1801aefb28dfe0c8f286432356d06857101a67a432497c5849111db2792fa0ee4ffff49a9124c152bcff82da1951258f989681e4f1338357f2c9f82333f6051b188f640bf200a0a75be1d35d2301e8d3813f7ba1926a28a0df05c21413cc0c4090c1e4ba4877dca8e129876c72ab3a801b4093320f5f685120680541d97889eea5dfcaf07a7ecb00c0ba0ae193969a4cddcbd753609a5304ea88783358ab0ae005c6af27bb58b2c4282186461ea50540845e2e2a2f4efced88c8ab9cd9fb4a226a265714c77ce7b79d1a40bd00b24cbba498dafde6bbad91686cf2e13e75669234bc342218887ba910ac81680122ddd36466e7e8a983d5a0fc18a6e9a386762c32132be08abe5554e334ec7d88734cfad9a378553b71222c55f6aa114392e015dfa2bb6cb4ad241c6bca82fdd0a00eb8d6b4afac61268130dea2807a97e4c0adc0e2be39abccbe64dca5c480e09c4bebb8b598e4f60afb0e92dce710859013b1ffa9c78fbf380160f31b1e72340dea86d353ff0e95884b72e2c2c10f6eff5f36c588ee845b7bc97c3b6bec4aa879dd0eb56b838b7bc2ec6e66a5b5517908197a67566dce7df421a8daedc98848c70d1d2c39b2f3538e6f17800bee3d3",
|
||||
"df0000000108277148f2b916666000403a52317841946860def0d7829c06fec03ffe8b97f84e10116fafd93d1d2d39bbfda0d148778c21bb1e1667eb789b1ff70c2e3d557ed9c31570d20d",
|
||||
"d00000000108277148f2b91666600044cd5e860e3fa7ac4769ec75d9b7d20f19e69265939a42afd3c4248a7f5210358a044f42869567e72a05642e96ddffa67bf24ec2d966d860c6accdee01d6917c8c43d4d089d8bb63ff848b617c13fbeefafcbb049ab0822a9ca7716c95af84d019b755b145dfe43c218555d1a7e047deda7d8db352a386b2b6d03f2e7f4510f47ff4ab199348dfa81c86bea5d09d7c7af4ef3f04e99fe4e6c21d53c4335407e27913129152033f17580f97d0345c8487a7ad329dc5c97b298ec7b80fee7813f1d6f94945a44ff662a69453c2dc7ac5e8a1cb90400e63818632d7f9654f140a61280df183b3d9f9b824e53d82f2c14ea3de89befdc79b84a4a3eb659a41db25622add94f2ad4b0d5977f1091aae0a4b83c7b41bca61c6c8d807ef02a8ce6240b76d442559a8b338b39418d27e99aff38840fc79a20995af65b3bbe1e3177074079a47578c51655a4016363364fd2c108d384e602deebd022da3c814549cd57d73c5bfc20e279045e2ad436fbd7e7c9e1985f0ec2f422e310e7aa8cfc48e637f9ac61d06d6482cb40b4376ff3c7abff3c3c26634689ae16d704bab1343d6413fc7b6c076eb0454eda2e0d1e077db40c922ebba6b0b1fa814e3ba76d8d6c4289abbd655f0cf5968eb2aba7131680b44da8910056a76647a6dfea95f27364a7ce694b8fbe19ebcb2a47e7350d33a36f7f5ca67af5e934f449125f4aae870a5b23b4370680ee02b194784d5d188ecdf58ae5454221406bde0ddd3e50d3363a564d6ca9fe0fb57d4df8716cb430cf553be573aa690e5645075ec74edd38cf23215bd50bcda0639dfbbe08dd6c476249e35da819ea6ccef808911b0eef6efaa4947244472795bc071d7154ed87e4a43575b3d61a551fcfccfb7ca3edaee9324f33f54dc9809747e59e24e79f256e8e72f01b8647f71c4b9dc260715fd9d83d3dbd9e124c432c04b3398e74efa3869fe129e368c15b6ca234a243fcac675adcc1db247e3f8485ac4a78f4a1ce2db3b437a1960b02f0c227901d165dcc05abdb3929a80dff2eaf72816185d4af4e28eea05430b736ddd2962e03ec64fa48649dd610e0e221c48f781b45cd9963c176126110e662369874e6a55f28039a23484c5c53714fc2d2030b48f1c895102ca9ad8acae1ec4eb0ae8d8bde31cd74fc515930078d22ad07dc3c7221ddbc4027c746207fa038b31080714091459c9a66ba4f5912d8d3905d3a9a47e4d8829a8110c96c0c9c81291c7985073808814109364df15b04520dc07e8d67cafcda71f0ca59423df5fadae92417a8661b3cdbbf6b1059780fb8b43eb4dcdacf731bb8db26294f978f6be7506b87d17a95367cdb83000565a4986e66dd60d0851f9b593d68790f8097434f62ea7a7396017c3c84754845d3a97f028cf8697d929a2826451653ccf84aba4d2f40fa530b258c13f08c6523c3c02d9669fd46b6a51f20ad323857d767150e3530a66bf88976dbadf99aeea549254c07e11e14085979b60f3b7e1728a4a2d7a35b0377c6501ae7d1d4bba338fb51a17ca8f7e698bd70cd01e8f30edf3e83591a2eb0038811e347bfcfab159b0d1ff6153e0f9ee4c129cfb7687e30b82eed74130c466eee06506dde50805b58c2acccd4cf4b2cc86c52fa2af602a8a7064eb9d90e1c568373b19e43ef4e7c1e4e1c9a58ccedf80a02a46ed64e68e72d4e75c7436e2bc0ba59f95a00456e5680af9e6cf4bf3a6d302ddaf8847cfd5ea606797",
|
||||
"d30000000108277148f2b91666600043d24b66b2531ed9f9c13b07b2654186b0410a608592fdf728479734933197ec06a1cde860f36b3170fb2a9c85c62a7867ba6520dcb2d0ab2f6a484d9ebf8237d7a6f3c1fb16c1e0458ccf20e6d1b298a7530cea42636166027d92812915e76fbcc436a5e414147672dd7b0d19ff24513800e63cd86984f1c93ef1430bb848d37830eed61675d7c9999b92c6e5796d384554c74dd5a163de341ab309d6b0cb028aa08e56d79c60980d4a49a1c095456ca119fc3f04e496c93a084d017f60c6e031d6e9ad2e4fa699bb4b0c92fdcb44131129db0d30ce9efb740d3db0339127d9bdd1d4f677b1cb532a33647851ba9bb20bd8d6aa593271a85c3a9dc9835065663e61faa8dc6af209a0caf183d0fda3d4839d40edd5659dd053778642db8fba21f1f793e45c5c517e68bbef8543e3a727743c7bf87d047d441d13226b9021fac56904872774cf6768dc91db8ea489a244500e9e527acdc0088437357acf9397b014e66fef2db1248f9c6a578af07d7a02b1356fee02e27b8207e57633fa7bfd87ccd382e368c14b946aea780fcbe696d6e4fa3aa589184e104177db2fc3d91d4af120d9da3bdad021d003796b8261b590d8113f995dc1db4fac1c62cb68370d41cc87c982815017ae2143d5a469b742d019e5556d813877fec9d021cb37f80e5987d9f743c2b39093a34f6654164a8185a5caefbbef8ea17f62f6801a3fd89fae333c878cec9b25d10dfee2abca65d7c909ad2e4f11736d13b1642df4c5a0761f8f29f35f37def9ed327f4a9d8e53269fa6c7cedd0f4fd67d6cde81934e291d9fca695cc9745890cb54503e29e09f4a30f80e2f574bbbeedb7d20481c583d8362d22b2dbec09494095a043cdae283e86f905d8807f7b7c0f06ce968487bbca1e20b87245b68f24537a7c7e768c838f1bf26650afdabec2c0bb9736b345473f279c9b73ecf0d2c4aea49330ecfef0949ef7cb81861b05950ec0772db856365b136ba75d5509c01d7a970c84ebc77d8d5c3ceae1ef5f3079afc7d78965ffa3bc4c64ef1b4718ffb488a571528c83b615c43022616bb4c494c838b556df5ede711a688b0315c1ce6e2892247df582b7c3f2b06cac0bd8d670e2b581f074750596ba162189060b8af3dfc650ba3b45932edc4f94f08741d3072bfd1ef8159b27a7f3673a4fc504304c12116e3c2d7636c663c9fa1b2f5571be88769f33ccb94a09abd9c5a7dc8a8c2031bb2bc256b84aeb68a9abf7673151cec41b48bdd74f395a46acf30dae43e060e596bb2e739274210701ee9bb6cc3ff81ace751e375a01f17b3c5cc5f1234c488d69611bb27f6e3ee17e3c3843ebe4a280d6aa8ef017058a872810a437f85331adb3cb8d382650897b1b1589ee6",
|
||||
"dd0000000108277148f2b9166660004053972df1beb451f73eb070e33ed63f681eb9b7e1e03f20baff3f54157598c7dd90a0de49850a3ccd6eb1b1cfc9dc6d3ba9ed1c0a19c69bf433da300d3cecc4ef151c44a721d680e3e3aaaf3eefec23091c5fde22",
|
||||
"c90000000108277148f2b916666000404700403986db57eaa4b165be8ab9c95452bddb922eb35b7610a8e664f6b4620d870507c241290ce885c36d7672c51d94063bf893e01bc79e1d81bf023338da3d22f63bc7aa433f99448825ee873013b006b2a6c87c7581c7117bfeb4ec3d68405a68d9488f6d58474dd16539677e869812ead055e70d655a660062e17083995c0dbecd565c79800b11c8ca0c351ecffa61e707d62d443b3810bc60d4ef87aa99b979ff55ee1ea46b65436c15534e5315113138aed6daa9f04d3050d77a7e379c83b948d3797177c1793e59b2555423bd52595d93e293ea8ffa3c428c6dbba4e202d76933caf6a5609b0a4aa6cf4fd2aadb6505382381ef2d5b33efc43eba24c84b7805baf2ddab44a50180e5e6f2a31f9ea8089aef562d3b578a799d61befec99c016fadec3363f68a1be4ca1e13e8bdd2809a1dacc41134663e22f21978167c5ee8ef49652ae152fc6c1bcf52109cd3076cdd599cb43261941de7aed148d7d3e956cd615549a9647496f43f998daad4c841cc40ce1501fbfc152b957c94be558f6743061e312d746137db2ae6a44e181587dbf6b0d9508cef4aefd99ea5d3369898bd4c3df5e95ac89eaaba54019ffe0402b8f567c91b9371e80c621c67d3c831331acc063892bbd8a81cfc0498e78474b11e8c05dd8f540c449505342ae95f6281940aae973db35b8e31ff801f6bc8975f592538881ae9cc4cedcbdb39a784a9fe962a1f12be51c11b91d4dedf649bb5672dec8e03db97b0d69fce36edfbecb6836644bad1ab8e6d4e13644d9c3476db0e8a8eb4b5a5c32f7a5604c8e19700c53602839478531579cb4c4bb5cc969cf482f325dd837629318baf128920d9978e23296d7016e6c05c954f95881b4f9f7e43bcea393951e91af0e4a671400dc435bd2a1616c60618df2476d0ece060dbbda11e751e256956a0dbcd7e4a8d6d85a3319f22a2c5f26dad50e82f70f3dd91feff19c775aa60499a3b7daa57e344c07c3787e99d53303488801d2b17cdbfdee61ea3fc473f6c146f06eb60d70594a59e0ed79cee6ca4a5f78b037637ddab69fb8522c0f7bf37aa7f59cc7fa659e759db69966455944975cd22a1a1355f35a589a4978c8f3272e1c4f6793288a00ab879299aa6ad02d966e3dc67cee0c808b1a046458cff9bdac25a4071eb10038a6389a0ef7233003641bd4ee1efad0e9b2f693396a89ca0db3c05b6abfed3b246eb1b23a6b77e8b486f26d9c3dde9dd6f3637a8115940ed2ca762ca6320609f61c37ffc9c3f2f7a0f27edc9891c2eeac49ba258a0d09c35c4fe1dc52d4d9319aa9b1a271a5d8d2d3a75fef4d59fb04679ba526aecbd19d73f72fee537630444326e2543ce564c669bf378499738385dda9ac63521a1b91f580d0737a7326009f0ff0dcb05aa8b86222c934d9ddb4628e30b6e12ae370154ab39c605431b4c40683592afcfd6fccf35df9fe5850442595d24be3d9f4298bf3d541f09e7e71f552c88eed9642df46953622d5aea05b5060325304ec81c0447ac95b90f9da4359e3286938f06aea3d45030cb836be15b1c65e3edf44cbcfe2f01ef8d7209c69d7c81334c866ebee50e418a28336cea1982069b4df090eab81303761d1af337e083f1e0ad1440a02ef1eefb03506c39d2377807e335ee64bdb76527f786223cee5233299eda9fcb1d38f19c34480f790a328b0735f80908e3aa70086df828d56b6c79516f71a24c9d94f60335f86e9d29c0c5d3872b",
|
||||
"dd0000000108277148f2b916666000406672db10ab41db38c01f7021709bac4d1659d872623eb5852b12b494535d13779a88d37e9685da572f6b2de35793a519a457493456ac4ee242933cf92d783f783656899c31832274bf1c26d24720d9d8ecfec598e19c58a478d2991dfc1cda3000f7bd7bd17e80",
|
||||
"d60000000108277148f2b916666000404ed98b1b4ac35c0c0ef18c88adf08a6701ccb0876ea75aac8c128349936fa3cb6728e4e58de8673dd7dc8457b092957f26bc8194233bb81c7e78127844f9b833f196dc46c5cb4064c773f3c6e0bc73",
|
||||
},
|
||||
domain: "www.google.com",
|
||||
},
|
||||
}
|
||||
q, err := NewQuicSniffer(SnifferConfig{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
for _, test := range cases {
|
||||
pkt, err := hex.DecodeString(test.input)
|
||||
data, err := testQuicSniffer(test.input, true)
|
||||
assert.NoError(t, err)
|
||||
oriPkt := bytes.Clone(pkt)
|
||||
domain, err := q.SniffData(pkt)
|
||||
assert.Equal(t, test.domain, data)
|
||||
|
||||
data, err = testQuicSniffer(test.input, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.domain, domain)
|
||||
assert.Equal(t, oriPkt, pkt) // ensure input data not changed
|
||||
assert.Equal(t, test.domain, data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -75,7 +75,15 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||
|
||||
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
|
||||
|
||||
ecdheKey := uConn.HandshakeState.State13.EcdheKey
|
||||
keyShareKeys := uConn.HandshakeState.State13.KeyShareKeys
|
||||
if keyShareKeys == nil {
|
||||
// WTF???
|
||||
if retry > 2 {
|
||||
return nil, errors.New("nil keyShareKeys")
|
||||
}
|
||||
continue // retry
|
||||
}
|
||||
ecdheKey := keyShareKeys.Ecdhe
|
||||
if ecdheKey == nil {
|
||||
// WTF???
|
||||
if retry > 2 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -428,12 +428,6 @@ type RawConfig struct {
|
||||
ClashForAndroid RawClashForAndroid `yaml:"clash-for-android" json:"clash-for-android"`
|
||||
}
|
||||
|
||||
var (
|
||||
GroupsList = list.New()
|
||||
ProxiesList = list.New()
|
||||
ParsingProxiesCallback func(groupsList *list.List, proxiesList *list.List)
|
||||
)
|
||||
|
||||
// Parse config
|
||||
func Parse(buf []byte) (*Config, error) {
|
||||
rawCfg, err := UnmarshalRawConfig(buf)
|
||||
@@ -927,12 +921,6 @@ func parseProxies(cfg *RawConfig) (proxies map[string]C.Proxy, providersMap map[
|
||||
)
|
||||
proxies["GLOBAL"] = adapter.NewProxy(global)
|
||||
}
|
||||
ProxiesList = proxiesList
|
||||
GroupsList = groupsList
|
||||
if ParsingProxiesCallback != nil {
|
||||
// refresh tray menu
|
||||
go ParsingProxiesCallback(GroupsList, ProxiesList)
|
||||
}
|
||||
return proxies, providersMap, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const (
|
||||
Tuic
|
||||
Ssh
|
||||
Mieru
|
||||
AnyTLS
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -152,7 +153,6 @@ type ProxyAdapter interface {
|
||||
|
||||
type Group interface {
|
||||
URLTest(ctx context.Context, url string, expectedStatus utils.IntRanges[uint16]) (mp map[string]uint16, err error)
|
||||
GetProxies(touch bool) []Proxy
|
||||
Touch()
|
||||
}
|
||||
|
||||
@@ -229,6 +229,8 @@ func (at AdapterType) String() string {
|
||||
return "Ssh"
|
||||
case Mieru:
|
||||
return "Mieru"
|
||||
case AnyTLS:
|
||||
return "AnyTLS"
|
||||
case Relay:
|
||||
return "Relay"
|
||||
case Selector:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,10 @@ type Sniffer interface {
|
||||
SupportPort(port uint16) bool
|
||||
}
|
||||
|
||||
type MultiPacketSniffer interface {
|
||||
WrapperSender(packetSender constant.PacketSender, override bool) constant.PacketSender
|
||||
}
|
||||
|
||||
const (
|
||||
TLS Type = iota
|
||||
HTTP
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -23,28 +24,31 @@ func dnsReadConfig() (servers []string, err error) {
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var ip net.IP
|
||||
var ip netip.Addr
|
||||
switch sa := sa.(type) {
|
||||
case *syscall.SockaddrInet4:
|
||||
ip = net.IPv4(sa.Addr[0], sa.Addr[1], sa.Addr[2], sa.Addr[3])
|
||||
ip = netip.AddrFrom4(sa.Addr)
|
||||
case *syscall.SockaddrInet6:
|
||||
ip = make(net.IP, net.IPv6len)
|
||||
copy(ip, sa.Addr[:])
|
||||
if ip[0] == 0xfe && ip[1] == 0xc0 {
|
||||
if sa.Addr[0] == 0xfe && sa.Addr[1] == 0xc0 {
|
||||
// Ignore these fec0/10 ones. Windows seems to
|
||||
// populate them as defaults on its misc rando
|
||||
// interfaces.
|
||||
continue
|
||||
}
|
||||
ip = netip.AddrFrom16(sa.Addr)
|
||||
if sa.ZoneId != 0 {
|
||||
ip = ip.WithZone(strconv.FormatInt(int64(sa.ZoneId), 10))
|
||||
}
|
||||
//continue
|
||||
default:
|
||||
// Unexpected type.
|
||||
continue
|
||||
}
|
||||
if slices.Contains(servers, ip.String()) {
|
||||
ipStr := ip.String()
|
||||
if slices.Contains(servers, ipStr) {
|
||||
continue
|
||||
}
|
||||
servers = append(servers, ip.String())
|
||||
servers = append(servers, ipStr)
|
||||
}
|
||||
}
|
||||
return
|
||||
|
||||
168
docs/config.yaml
168
docs/config.yaml
@@ -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服务器
|
||||
@@ -448,6 +449,26 @@ proxies: # socks5
|
||||
password: "shadow_tls_password"
|
||||
version: 2 # support 1/2/3
|
||||
|
||||
- name: "ss5"
|
||||
type: ss
|
||||
server: server
|
||||
port: 443
|
||||
cipher: chacha20-ietf-poly1305
|
||||
password: "password"
|
||||
plugin: gost-plugin
|
||||
plugin-opts:
|
||||
mode: websocket
|
||||
# tls: true # wss
|
||||
# 可使用 openssl x509 -noout -fingerprint -sha256 -inform pem -in yourcert.pem 获取
|
||||
# 配置指纹将实现 SSL Pining 效果
|
||||
# fingerprint: xxxx
|
||||
# skip-cert-verify: true
|
||||
# host: bing.com
|
||||
# path: "/"
|
||||
# mux: true
|
||||
# headers:
|
||||
# custom: value
|
||||
|
||||
- name: "ss-restls-tls13"
|
||||
type: ss
|
||||
server: [YOUR_SERVER_IP]
|
||||
@@ -747,6 +768,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"
|
||||
@@ -853,8 +879,28 @@ proxies: # socks5
|
||||
port: 2999
|
||||
# port-range: 2090-2099 #(不可同时填写 port 和 port-range)
|
||||
transport: TCP # 只支持 TCP
|
||||
udp: true # 支持 UDP over 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"
|
||||
@@ -973,6 +1019,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
|
||||
@@ -1012,6 +1069,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
|
||||
@@ -1052,7 +1117,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 处理
|
||||
@@ -1060,20 +1125,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 名称必须合法,否则会出错)
|
||||
@@ -1081,17 +1152,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 名称必须合法,否则会出错)
|
||||
@@ -1099,7 +1173,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 名称必须合法,否则会出错)
|
||||
@@ -1108,7 +1182,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 名称必须合法,否则会出错)
|
||||
@@ -1117,13 +1191,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 名称必须合法,否则会出错)
|
||||
@@ -1143,13 +1226,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
|
||||
|
||||
75
go.mod
75
go.mod
@@ -6,59 +6,62 @@ 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/enfein/mieru/v3 v3.8.3
|
||||
github.com/go-chi/chi/v5 v5.1.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/enfein/mieru/v3 v3.13.0
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/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 // lastest version compatible with golang1.20
|
||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/bart v0.19.0
|
||||
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.3-0.20241126053724-b69fea3888da
|
||||
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.4.2
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04
|
||||
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-20241006021335-daedaf0ca7aa
|
||||
github.com/metacubex/utls v1.6.6
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/metacubex/utls v1.6.8-alpha.4
|
||||
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.1
|
||||
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.1
|
||||
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 // lastest version compatible with golang1.20
|
||||
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.29.0
|
||||
golang.org/x/crypto v0.33.0 // lastest version compatible with golang1.20
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/sys v0.27.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
golang.org/x/net v0.35.0 // lastest version compatible with golang1.20
|
||||
golang.org/x/sys v0.30.0 // lastest version compatible with golang1.20
|
||||
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0
|
||||
lukechampine.com/blake3 v1.3.0 // lastest version compatible with golang1.20
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -69,6 +72,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
|
||||
@@ -82,13 +86,12 @@ require (
|
||||
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-20241126021258-5b028898cc5a // indirect
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b // 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
|
||||
@@ -97,8 +100,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
|
||||
@@ -111,12 +112,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.9.0 // indirect
|
||||
golang.org/x/text v0.20.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
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
|
||||
google.golang.org/grpc v1.64.1 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a
|
||||
|
||||
135
go.sum
135
go.sum
@@ -21,14 +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/enfein/mieru/v3 v3.8.3 h1:s4K0hMFDg6LHltokR8/nBTVCq15XnnxPsvc1LrHwpoo=
|
||||
github.com/enfein/mieru/v3 v3.8.3/go.mod h1:YtU00qjAEt54mCBQu4WZPCey6cBdB1BUtXjvrHLEUNQ=
|
||||
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.13.0 h1:eGyxLGkb+lut9ebmx+BGwLJ5UMbEc/wGIYO0AXEKy98=
|
||||
github.com/enfein/mieru/v3 v3.13.0/go.mod h1:zJBUCsi5rxyvHM8fjFf+GLaEl4OEjjBXr1s5F6Qd3hM=
|
||||
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=
|
||||
@@ -42,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=
|
||||
@@ -58,10 +59,10 @@ 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.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
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=
|
||||
@@ -70,22 +71,20 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
||||
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,42 +97,47 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31UC14YFNr78pESt5Vowlc62zziw05JCUqoL4=
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY=
|
||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.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-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.48.3-0.20241126053724-b69fea3888da h1:Mq6cbHbPTLLTUfA9scrwBmOGkvl6y99E3WmtMIMqo30=
|
||||
github.com/metacubex/quic-go v0.48.3-0.20241126053724-b69fea3888da/go.mod h1:AiZ+UPgrkO1DTnmiAX4b+kRoV1Vfc65UkYD7RbFlIZA=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b/go.mod h1:8LpS0IJW1VmWzUm3ylb0e2SK5QDm5lO/2qwWLZgRpBU=
|
||||
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-20241121030428-33b6ebc52000 h1:gUbMXcQXhXGj0vCpCVFTUyIH7TMpD1dpTcNv/MCS+ok=
|
||||
github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000/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.4.2 h1:fwrQp3P536Pswu6gR1FJ+8GH55e+t2+B8LHIjwRtWbc=
|
||||
github.com/metacubex/sing-tun v0.4.2/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9 h1:OAXiCosqY8xKDp3pqTW3qbrCprZ1l6WkrXSFSCwyY4I=
|
||||
github.com/metacubex/sing-vmess v0.1.9-0.20240719134745-1df6fb20bbf9/go.mod h1:olVkD4FChQ5gKMHG4ZzuD7+fMkJY1G8vwOKpRehjrmY=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/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-20241006021335-daedaf0ca7aa h1:9mcjV+RGZVC3reJBNDjjNPyS8PmFG97zq56X7WNaFO4=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241006021335-daedaf0ca7aa/go.mod h1:4tLB5c8U0CxpkFM+AJJB77jEaVDbLH5XQvy42vAGsWw=
|
||||
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/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.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
||||
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=
|
||||
@@ -154,8 +158,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.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
|
||||
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
@@ -168,19 +172,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=
|
||||
@@ -199,8 +200,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=
|
||||
@@ -232,8 +234,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.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
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=
|
||||
@@ -242,11 +244,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.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
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.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.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=
|
||||
@@ -258,17 +260,16 @@ 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.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
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.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
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=
|
||||
@@ -276,14 +277,10 @@ golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
|
||||
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
|
||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
||||
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=
|
||||
|
||||
190
listener/anytls/server.go
Normal file
190
listener/anytls/server.go
Normal file
@@ -0,0 +1,190 @@
|
||||
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
|
||||
}
|
||||
|
||||
// It seems that mihomo does not implement a connection error reporting mechanism, so we report success directly.
|
||||
err = stream.HandshakeSuccess()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h.NewConnection(ctx, stream, M.Metadata{
|
||||
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
Destination: destination,
|
||||
})
|
||||
}, &l.padding)
|
||||
session.Run()
|
||||
session.Close()
|
||||
}
|
||||
19
listener/config/anytls.go
Normal file
19
listener/config/anytls.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type AnyTLSServer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||
}
|
||||
|
||||
func (t AnyTLSServer) String() string {
|
||||
b, _ := json.Marshal(t)
|
||||
return string(b)
|
||||
}
|
||||
16
listener/config/auth.go
Normal file
16
listener/config/auth.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
)
|
||||
|
||||
// AuthServer for http/socks/mixed server
|
||||
type AuthServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
AuthStore auth.AuthStore
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
}
|
||||
@@ -23,6 +23,12 @@ type Hysteria2Server struct {
|
||||
CWND int `yaml:"cwnd" json:"cwnd,omitempty"`
|
||||
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
38
listener/config/trojan.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
)
|
||||
|
||||
type TrojanUser struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type TrojanServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []TrojanUser
|
||||
WsPath string
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption
|
||||
TrojanSSOption TrojanSSOption
|
||||
}
|
||||
|
||||
// TrojanSSOption from https://github.com/p4gefau1t/trojan-go/blob/v0.10.6/tunnel/shadowsocks/config.go#L5
|
||||
type TrojanSSOption struct {
|
||||
Enabled bool
|
||||
Method string
|
||||
Password string
|
||||
}
|
||||
|
||||
func (t TrojanServer) String() string {
|
||||
b, _ := json.Marshal(t)
|
||||
return string(b)
|
||||
}
|
||||
31
listener/config/vless.go
Normal file
31
listener/config/vless.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
)
|
||||
|
||||
type VlessUser struct {
|
||||
Username string
|
||||
UUID string
|
||||
Flow string
|
||||
}
|
||||
|
||||
type VlessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VlessUser
|
||||
WsPath string
|
||||
GrpcServiceName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||
}
|
||||
|
||||
func (t VlessServer) String() string {
|
||||
b, _ := json.Marshal(t)
|
||||
return string(b)
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
package config
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -47,7 +47,7 @@ func removeExtraHTTPHostPort(req *http.Request) {
|
||||
host = req.URL.Host
|
||||
}
|
||||
|
||||
if pHost, port, err := net.SplitHostPort(host); err == nil && (port == "80" || port == "443") {
|
||||
if pHost, port, err := net.SplitHostPort(host); err == nil && port == "80" {
|
||||
host = pHost
|
||||
if ip, err := netip.ParseAddr(pHost); err == nil && ip.Is6() {
|
||||
// RFC 2617 Sec 3.2.2, for IPv6 literal
|
||||
|
||||
82
listener/inbound/anytls.go
Normal file
82
listener/inbound/anytls.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package inbound
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/listener/anytls"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
type AnyTLSOption struct {
|
||||
BaseOption
|
||||
Users map[string]string `inbound:"users,omitempty"`
|
||||
Certificate string `inbound:"certificate"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
PaddingScheme string `inbound:"padding-scheme,omitempty"`
|
||||
}
|
||||
|
||||
func (o AnyTLSOption) Equal(config C.InboundConfig) bool {
|
||||
return optionToString(o) == optionToString(config)
|
||||
}
|
||||
|
||||
type AnyTLS struct {
|
||||
*Base
|
||||
config *AnyTLSOption
|
||||
l C.MultiAddrListener
|
||||
vs LC.AnyTLSServer
|
||||
}
|
||||
|
||||
func NewAnyTLS(options *AnyTLSOption) (*AnyTLS, error) {
|
||||
base, err := NewBase(&options.BaseOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &AnyTLS{
|
||||
Base: base,
|
||||
config: options,
|
||||
vs: LC.AnyTLSServer{
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Users: options.Users,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
PaddingScheme: options.PaddingScheme,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Config implements constant.InboundListener
|
||||
func (v *AnyTLS) Config() C.InboundConfig {
|
||||
return v.config
|
||||
}
|
||||
|
||||
// Address implements constant.InboundListener
|
||||
func (v *AnyTLS) Address() string {
|
||||
var addrList []string
|
||||
if v.l != nil {
|
||||
for _, addr := range v.l.AddrList() {
|
||||
addrList = append(addrList, addr.String())
|
||||
}
|
||||
}
|
||||
return strings.Join(addrList, ",")
|
||||
}
|
||||
|
||||
// Listen implements constant.InboundListener
|
||||
func (v *AnyTLS) Listen(tunnel C.Tunnel) error {
|
||||
var err error
|
||||
v.l, err = anytls.New(v.vs, tunnel, v.Additions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infoln("AnyTLS[%s] proxy listening at: %s", v.Name(), v.Address())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close implements constant.InboundListener
|
||||
func (v *AnyTLS) Close() error {
|
||||
return v.l.Close()
|
||||
}
|
||||
|
||||
var _ C.InboundListener = (*AnyTLS)(nil)
|
||||
@@ -5,8 +5,10 @@ import (
|
||||
"net"
|
||||
"net/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"`
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
23
listener/inbound/reality.go
Normal file
23
listener/inbound/reality.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package inbound
|
||||
|
||||
import "github.com/metacubex/mihomo/listener/reality"
|
||||
|
||||
type RealityConfig struct {
|
||||
Dest string `inbound:"dest"`
|
||||
PrivateKey string `inbound:"private-key"`
|
||||
ShortID []string `inbound:"short-id"`
|
||||
ServerNames []string `inbound:"server-names"`
|
||||
MaxTimeDifference int `inbound:"max-time-difference,omitempty"`
|
||||
Proxy string `inbound:"proxy,omitempty"`
|
||||
}
|
||||
|
||||
func (c RealityConfig) Build() reality.Config {
|
||||
return reality.Config{
|
||||
Dest: c.Dest,
|
||||
PrivateKey: c.PrivateKey,
|
||||
ShortID: c.ShortID,
|
||||
ServerNames: c.ServerNames,
|
||||
MaxTimeDifference: c.MaxTimeDifference,
|
||||
Proxy: c.Proxy,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
package inbound
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
113
listener/inbound/trojan.go
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
102
listener/inbound/vless.go
Normal 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)
|
||||
@@ -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
|
||||
|
||||
@@ -512,8 +512,8 @@ func ReCreateTun(tunConf LC.Tun, tunnel C.Tunnel) {
|
||||
}()
|
||||
|
||||
if tunConf.Equal(LastTunConf) {
|
||||
if tunLister != nil {
|
||||
tunLister.FlushDefaultInterface()
|
||||
if tunLister != nil { // some default value in dialer maybe changed when config reload, reset at here
|
||||
tunLister.OnReload()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
104
listener/reality/reality.go
Normal 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
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
24
listener/sing/util.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package sing_tun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/component/iface"
|
||||
|
||||
"github.com/sagernet/sing/common/control"
|
||||
"github.com/metacubex/sing-tun/control"
|
||||
)
|
||||
|
||||
type defaultInterfaceFinder struct{}
|
||||
@@ -15,7 +15,8 @@ var DefaultInterfaceFinder control.InterfaceFinder = (*defaultInterfaceFinder)(n
|
||||
|
||||
func (f *defaultInterfaceFinder) Update() error {
|
||||
iface.FlushCache()
|
||||
return nil
|
||||
_, err := iface.Interfaces()
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *defaultInterfaceFinder) Interfaces() []control.Interface {
|
||||
@@ -31,45 +32,46 @@ func (f *defaultInterfaceFinder) Interfaces() []control.Interface {
|
||||
return interfaces
|
||||
}
|
||||
|
||||
var errNoSuchInterface = errors.New("no such network interface")
|
||||
|
||||
func (f *defaultInterfaceFinder) InterfaceIndexByName(name string) (int, error) {
|
||||
ifaces, err := iface.Interfaces()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
func (f *defaultInterfaceFinder) ByName(name string) (*control.Interface, error) {
|
||||
netInterface, err := iface.ResolveInterface(name)
|
||||
if err == nil {
|
||||
return (*control.Interface)(netInterface), nil
|
||||
}
|
||||
for _, netInterface := range ifaces {
|
||||
if netInterface.Name == name {
|
||||
return netInterface.Index, nil
|
||||
if _, err := net.InterfaceByName(name); err == nil {
|
||||
err = f.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.ByName(name)
|
||||
}
|
||||
return 0, errNoSuchInterface
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (f *defaultInterfaceFinder) InterfaceNameByIndex(index int) (string, error) {
|
||||
ifaces, err := iface.Interfaces()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, netInterface := range ifaces {
|
||||
if netInterface.Index == index {
|
||||
return netInterface.Name, nil
|
||||
}
|
||||
}
|
||||
return "", errNoSuchInterface
|
||||
}
|
||||
|
||||
func (f *defaultInterfaceFinder) InterfaceByAddr(addr netip.Addr) (*control.Interface, error) {
|
||||
func (f *defaultInterfaceFinder) ByIndex(index int) (*control.Interface, error) {
|
||||
ifaces, err := iface.Interfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, netInterface := range ifaces {
|
||||
for _, prefix := range netInterface.Addresses {
|
||||
if prefix.Contains(addr) {
|
||||
return (*control.Interface)(netInterface), nil
|
||||
}
|
||||
if netInterface.Index == index {
|
||||
return (*control.Interface)(netInterface), nil
|
||||
}
|
||||
}
|
||||
return nil, errNoSuchInterface
|
||||
_, err = net.InterfaceByIndex(index)
|
||||
if err == nil {
|
||||
err = f.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f.ByIndex(index)
|
||||
}
|
||||
return nil, iface.ErrIfaceNotFound
|
||||
}
|
||||
|
||||
func (f *defaultInterfaceFinder) ByAddr(addr netip.Addr) (*control.Interface, error) {
|
||||
netInterface, err := iface.ResolveInterfaceByAddr(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*control.Interface)(netInterface), nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
tun "github.com/metacubex/sing-tun"
|
||||
"github.com/metacubex/sing-tun/control"
|
||||
"github.com/sagernet/sing/common"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
F "github.com/sagernet/sing/common/format"
|
||||
@@ -52,6 +53,8 @@ type Listener struct {
|
||||
autoRedirect tun.AutoRedirect
|
||||
autoRedirectOutputMark int32
|
||||
|
||||
cDialerInterfaceFinder dialer.InterfaceFinder
|
||||
|
||||
ruleUpdateCallbackCloser io.Closer
|
||||
ruleUpdateMutex sync.Mutex
|
||||
routeAddressMap map[string]*netipx.IPSet
|
||||
@@ -289,14 +292,31 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
return
|
||||
}
|
||||
l.defaultInterfaceMonitor = defaultInterfaceMonitor
|
||||
defaultInterfaceMonitor.RegisterCallback(func(event int) {
|
||||
l.FlushDefaultInterface()
|
||||
defaultInterfaceMonitor.RegisterCallback(func(defaultInterface *control.Interface, event int) {
|
||||
if defaultInterface != nil {
|
||||
log.Warnln("[TUN] default interface changed by monitor, => %s", defaultInterface.Name)
|
||||
} else {
|
||||
log.Errorln("[TUN] default interface lost by monitor")
|
||||
}
|
||||
iface.FlushCache()
|
||||
resolver.ResetConnection() // reset resolver's connection after default interface changed
|
||||
})
|
||||
err = defaultInterfaceMonitor.Start()
|
||||
if err != nil {
|
||||
err = E.Cause(err, "start DefaultInterfaceMonitor")
|
||||
return
|
||||
}
|
||||
|
||||
if options.AutoDetectInterface {
|
||||
l.cDialerInterfaceFinder = &cDialerInterfaceFinder{
|
||||
tunName: tunName,
|
||||
defaultInterfaceMonitor: defaultInterfaceMonitor,
|
||||
}
|
||||
if !dialer.DefaultInterfaceFinder.CompareAndSwap(nil, l.cDialerInterfaceFinder) {
|
||||
err = E.New("not allowed two tun listener using auto-detect-interface")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tunOptions := tun.Options{
|
||||
@@ -436,7 +456,10 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||
}
|
||||
if tunOptions.AutoRedirectMarkMode {
|
||||
l.autoRedirectOutputMark = int32(outputMark)
|
||||
dialer.DefaultRoutingMark.Store(l.autoRedirectOutputMark)
|
||||
if !dialer.DefaultRoutingMark.CompareAndSwap(0, l.autoRedirectOutputMark) {
|
||||
err = E.New("not allowed setting global routing-mark when working with autoRedirectMarkMode")
|
||||
return
|
||||
}
|
||||
l.autoRedirect.UpdateRouteAddressSet()
|
||||
l.ruleUpdateCallbackCloser = rpTunnel.RuleUpdateCallback().Register(l.ruleUpdateCallback)
|
||||
}
|
||||
@@ -503,27 +526,44 @@ func (l *Listener) updateRule(ruleProvider provider.RuleProvider, exclude bool,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Listener) FlushDefaultInterface() {
|
||||
if l.options.AutoDetectInterface && l.defaultInterfaceMonitor != nil {
|
||||
for _, destination := range []netip.Addr{netip.IPv4Unspecified(), netip.IPv6Unspecified(), netip.MustParseAddr("1.1.1.1")} {
|
||||
autoDetectInterfaceName := l.defaultInterfaceMonitor.DefaultInterfaceName(destination)
|
||||
if autoDetectInterfaceName == l.tunName {
|
||||
log.Warnln("[TUN] Auto detect interface by %s get same name with tun", destination.String())
|
||||
} else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" {
|
||||
log.Warnln("[TUN] Auto detect interface by %s get empty name.", destination.String())
|
||||
} else {
|
||||
if old := dialer.DefaultInterface.Swap(autoDetectInterfaceName); old != autoDetectInterfaceName {
|
||||
log.Warnln("[TUN] default interface changed by monitor, %s => %s", old, autoDetectInterfaceName)
|
||||
iface.FlushCache()
|
||||
resolver.ResetConnection() // reset resolver's connection after default interface changed
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if dialer.DefaultInterface.CompareAndSwap("", "<invalid>") {
|
||||
log.Warnln("[TUN] Auto detect interface failed, set '<invalid>' to DefaultInterface to avoid lookback")
|
||||
func (l *Listener) OnReload() {
|
||||
if l.autoRedirectOutputMark != 0 {
|
||||
dialer.DefaultRoutingMark.CompareAndSwap(0, l.autoRedirectOutputMark)
|
||||
}
|
||||
if l.cDialerInterfaceFinder != nil {
|
||||
dialer.DefaultInterfaceFinder.CompareAndSwap(nil, l.cDialerInterfaceFinder)
|
||||
}
|
||||
}
|
||||
|
||||
type cDialerInterfaceFinder struct {
|
||||
tunName string
|
||||
defaultInterfaceMonitor tun.DefaultInterfaceMonitor
|
||||
}
|
||||
|
||||
func (d *cDialerInterfaceFinder) DefaultInterfaceName(destination netip.Addr) string {
|
||||
if netInterface, _ := DefaultInterfaceFinder.ByAddr(destination); netInterface != nil {
|
||||
return netInterface.Name
|
||||
}
|
||||
if netInterface := d.defaultInterfaceMonitor.DefaultInterface(); netInterface != nil {
|
||||
return netInterface.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (d *cDialerInterfaceFinder) FindInterfaceName(destination netip.Addr) string {
|
||||
for _, dest := range []netip.Addr{destination, netip.IPv4Unspecified(), netip.IPv6Unspecified()} {
|
||||
autoDetectInterfaceName := d.DefaultInterfaceName(dest)
|
||||
if autoDetectInterfaceName == d.tunName {
|
||||
log.Warnln("[TUN] Auto detect interface for %s get same name with tun", destination.String())
|
||||
} else if autoDetectInterfaceName == "" || autoDetectInterfaceName == "<nil>" {
|
||||
log.Warnln("[TUN] Auto detect interface for %s get empty name.", destination.String())
|
||||
} else {
|
||||
log.Debugln("[TUN] Auto detect interface for %s --> %s", destination, autoDetectInterfaceName)
|
||||
return autoDetectInterfaceName
|
||||
}
|
||||
}
|
||||
log.Warnln("[TUN] Auto detect interface for %s failed, return '<invalid>' to avoid lookback", destination)
|
||||
return "<invalid>"
|
||||
}
|
||||
|
||||
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
||||
@@ -564,6 +604,9 @@ func (l *Listener) Close() error {
|
||||
if l.autoRedirectOutputMark != 0 {
|
||||
dialer.DefaultRoutingMark.CompareAndSwap(l.autoRedirectOutputMark, 0)
|
||||
}
|
||||
if l.cDialerInterfaceFinder != nil {
|
||||
dialer.DefaultInterfaceFinder.CompareAndSwap(l.cDialerInterfaceFinder, nil)
|
||||
}
|
||||
return common.Close(
|
||||
l.ruleUpdateCallbackCloser,
|
||||
l.tunStack,
|
||||
|
||||
210
listener/sing_vless/server.go
Normal file
210
listener/sing_vless/server.go
Normal file
@@ -0,0 +1,210 @@
|
||||
package sing_vless
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
"github.com/metacubex/sing-vmess/vless"
|
||||
utls "github.com/metacubex/utls"
|
||||
"github.com/sagernet/sing/common"
|
||||
"github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
func init() {
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*reality.Conn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn).Elem(), unsafe.Pointer(tlsConn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*utls.UConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
|
||||
})
|
||||
|
||||
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
|
||||
tlsConn, loaded := common.Cast[*tlsC.UConn](conn)
|
||||
if !loaded {
|
||||
return
|
||||
}
|
||||
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
|
||||
})
|
||||
}
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.VlessServer
|
||||
listeners []net.Listener
|
||||
service *vless.Service[string]
|
||||
}
|
||||
|
||||
func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-VLESS"),
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
h, err := sing.NewListenerHandler(sing.ListenerConfig{
|
||||
Tunnel: tunnel,
|
||||
Type: C.VLESS,
|
||||
Additions: additions,
|
||||
MuxOption: config.MuxOption,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
service := vless.NewService[string](log.SingLogger, h)
|
||||
service.UpdateUsers(
|
||||
common.Map(config.Users, func(it LC.VlessUser) string {
|
||||
return it.Username
|
||||
}),
|
||||
common.Map(config.Users, func(it LC.VlessUser) string {
|
||||
return it.UUID
|
||||
}),
|
||||
common.Map(config.Users, func(it LC.VlessUser) string {
|
||||
return it.Flow
|
||||
}))
|
||||
|
||||
sl = &Listener{false, config, nil, service}
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpHandler http.Handler
|
||||
|
||||
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 config.WsPath != "" {
|
||||
httpMux := http.NewServeMux()
|
||||
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
sl.HandleConn(conn, tunnel, additions...)
|
||||
})
|
||||
httpHandler = httpMux
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
|
||||
}
|
||||
if config.GrpcServiceName != "" {
|
||||
httpHandler = gun.NewServerHandler(gun.ServerOption{
|
||||
ServiceName: config.GrpcServiceName,
|
||||
ConnHandler: func(conn net.Conn) {
|
||||
sl.HandleConn(conn, tunnel, additions...)
|
||||
},
|
||||
HttpHandler: httpHandler,
|
||||
})
|
||||
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
|
||||
}
|
||||
|
||||
for _, addr := range strings.Split(config.Listen, ",") {
|
||||
addr := addr
|
||||
|
||||
//TCP
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if realityBuilder != nil {
|
||||
l = realityBuilder.NewListener(l)
|
||||
} else if len(tlsConfig.Certificates) > 0 {
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
} else {
|
||||
return nil, errors.New("disallow using Vless without both certificates/reality config")
|
||||
}
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
go func() {
|
||||
if httpHandler != nil {
|
||||
_ = http.Serve(l, httpHandler)
|
||||
return
|
||||
}
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if sl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
go sl.HandleConn(c, tunnel)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
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, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||
Protocol: "vless",
|
||||
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package sing_vmess
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -12,8 +13,10 @@ 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/reality"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
vmess "github.com/metacubex/sing-vmess"
|
||||
@@ -73,7 +76,8 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
sl = &Listener{false, config, nil, service}
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
var httpMux *http.ServeMux
|
||||
var realityBuilder *reality.Builder
|
||||
var httpHandler http.Handler
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||
@@ -82,18 +86,38 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
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 config.WsPath != "" {
|
||||
httpMux = http.NewServeMux()
|
||||
httpMux := http.NewServeMux()
|
||||
httpMux.HandleFunc(config.WsPath, func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := mihomoVMess.StreamUpgradedWebsocketConn(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
sl.HandleConn(conn, tunnel)
|
||||
sl.HandleConn(conn, tunnel, additions...)
|
||||
})
|
||||
httpHandler = httpMux
|
||||
tlsConfig.NextProtos = append(tlsConfig.NextProtos, "http/1.1")
|
||||
}
|
||||
if config.GrpcServiceName != "" {
|
||||
httpHandler = gun.NewServerHandler(gun.ServerOption{
|
||||
ServiceName: config.GrpcServiceName,
|
||||
ConnHandler: func(conn net.Conn) {
|
||||
sl.HandleConn(conn, tunnel, additions...)
|
||||
},
|
||||
HttpHandler: httpHandler,
|
||||
})
|
||||
tlsConfig.NextProtos = append([]string{"h2"}, tlsConfig.NextProtos...) // h2 must before http/1.1
|
||||
}
|
||||
|
||||
for _, addr := range strings.Split(config.Listen, ",") {
|
||||
addr := addr
|
||||
@@ -103,14 +127,16 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tlsConfig.Certificates) > 0 {
|
||||
if realityBuilder != nil {
|
||||
l = realityBuilder.NewListener(l)
|
||||
} else if len(tlsConfig.Certificates) > 0 {
|
||||
l = tls.NewListener(l, tlsConfig)
|
||||
}
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
go func() {
|
||||
if httpMux != nil {
|
||||
_ = http.Serve(l, httpMux)
|
||||
if httpHandler != nil {
|
||||
_ = http.Serve(l, httpHandler)
|
||||
return
|
||||
}
|
||||
for {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package socks
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
@@ -9,6 +11,8 @@ 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/reality"
|
||||
"github.com/metacubex/mihomo/transport/socks4"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
)
|
||||
@@ -36,10 +40,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
|
||||
@@ -49,14 +53,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)
|
||||
}
|
||||
|
||||
sl := &Listener{
|
||||
listener: l,
|
||||
addr: addr,
|
||||
addr: config.Listen,
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
@@ -67,7 +97,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()
|
||||
|
||||
@@ -40,12 +40,12 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
l, err := net.ListenPacket("udp", addr)
|
||||
l, err := inbound.ListenPacket("udp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := sockopt.UDPReuseaddr(l.(*net.UDPConn)); err != nil {
|
||||
if err := sockopt.UDPReuseaddr(l); 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
Reference in New Issue
Block a user