mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2026-03-04 04:47:30 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bc6f77e36 | ||
|
|
a7e56f1c43 | ||
|
|
05e8f13a8d | ||
|
|
136d114196 | ||
|
|
938ab7f44d | ||
|
|
a00f4f1108 | ||
|
|
1213023f11 | ||
|
|
3b40bf76b7 | ||
|
|
1dc4155195 | ||
|
|
d81c19a7c8 | ||
|
|
e2140e62ca | ||
|
|
8d783c65c1 | ||
|
|
91324b76d2 | ||
|
|
e23f40a56b | ||
|
|
5830afcbde | ||
|
|
e2b75b35bb | ||
|
|
06b9e6c367 | ||
|
|
dc1145a484 | ||
|
|
b151e7d69c | ||
|
|
808fdcf624 | ||
|
|
9962a0d091 | ||
|
|
ef29e4501e | ||
|
|
d1d846f1ab | ||
|
|
eaaccbc6dd | ||
|
|
447c416391 | ||
|
|
6d24ca9ae6 | ||
|
|
b52b7537fc | ||
|
|
3cc67fd759 | ||
|
|
9074b78e36 |
16
.github/mihomo.service
vendored
16
.github/mihomo.service
vendored
@@ -1,17 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
After=network.target NetworkManager.service systemd-networkd.service iwd.service
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
LimitNPROC=500
|
||||
LimitNOFILE=1000000
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_TIME CAP_SYS_PTRACE CAP_DAC_READ_SEARCH CAP_DAC_OVERRIDE
|
||||
Restart=always
|
||||
ExecStartPre=/usr/bin/sleep 2s
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
WantedBy=multi-user.target
|
||||
|
||||
17
.github/mihomo@.service
vendored
Normal file
17
.github/mihomo@.service
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
[Unit]
|
||||
Description=mihomo Daemon, Another Clash Kernel.
|
||||
Documentation=https://wiki.metacubex.one
|
||||
After=network.target nss-lookup.target network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE CAP_SYS_PTRACE CAP_DAC_READ_SEARCH
|
||||
ExecStart=/usr/bin/mihomo -d /etc/mihomo
|
||||
ExecReload=/bin/kill -HUP $MAINPID
|
||||
Restart=on-failure
|
||||
RestartSec=10
|
||||
LimitNOFILE=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
47
.github/workflows/build.yml
vendored
47
.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
|
||||
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -139,10 +139,13 @@ func (ranges IntRanges[T]) Range(f func(t T) bool) {
|
||||
}
|
||||
|
||||
for _, r := range ranges {
|
||||
for i := r.Start(); i <= r.End(); i++ {
|
||||
for i := r.Start(); i <= r.End() && i >= r.Start(); i++ {
|
||||
if !f(i) {
|
||||
return
|
||||
}
|
||||
if i+1 < i { // integer overflow
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ const (
|
||||
Tuic
|
||||
Ssh
|
||||
Mieru
|
||||
AnyTLS
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -229,6 +230,8 @@ func (at AdapterType) String() string {
|
||||
return "Ssh"
|
||||
case Mieru:
|
||||
return "Mieru"
|
||||
case AnyTLS:
|
||||
return "AnyTLS"
|
||||
case Relay:
|
||||
return "Relay"
|
||||
case Selector:
|
||||
|
||||
@@ -28,10 +28,12 @@ const (
|
||||
VLESS
|
||||
REDIR
|
||||
TPROXY
|
||||
TROJAN
|
||||
TUNNEL
|
||||
TUN
|
||||
TUIC
|
||||
HYSTERIA2
|
||||
ANYTLS
|
||||
INNER
|
||||
)
|
||||
|
||||
@@ -76,6 +78,8 @@ func (t Type) String() string {
|
||||
return "Redir"
|
||||
case TPROXY:
|
||||
return "TProxy"
|
||||
case TROJAN:
|
||||
return "Trojan"
|
||||
case TUNNEL:
|
||||
return "Tunnel"
|
||||
case TUN:
|
||||
@@ -84,6 +88,8 @@ func (t Type) String() string {
|
||||
return "Tuic"
|
||||
case HYSTERIA2:
|
||||
return "Hysteria2"
|
||||
case ANYTLS:
|
||||
return "AnyTLS"
|
||||
case INNER:
|
||||
return "Inner"
|
||||
default:
|
||||
@@ -112,6 +118,8 @@ func ParseType(t string) (*Type, error) {
|
||||
res = REDIR
|
||||
case "TPROXY":
|
||||
res = TPROXY
|
||||
case "TROJAN":
|
||||
res = TROJAN
|
||||
case "TUNNEL":
|
||||
res = TUNNEL
|
||||
case "TUN":
|
||||
@@ -120,6 +128,8 @@ func ParseType(t string) (*Type, error) {
|
||||
res = TUIC
|
||||
case "HYSTERIA2":
|
||||
res = HYSTERIA2
|
||||
case "ANYTLS":
|
||||
res = ANYTLS
|
||||
case "INNER":
|
||||
res = INNER
|
||||
default:
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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服务器
|
||||
@@ -863,6 +864,23 @@ proxies: # socks5
|
||||
# 可以使用的值包括 MULTIPLEXING_OFF, MULTIPLEXING_LOW, MULTIPLEXING_MIDDLE, MULTIPLEXING_HIGH。其中 MULTIPLEXING_OFF 会关闭多路复用功能。默认值为 MULTIPLEXING_LOW。
|
||||
# multiplexing: MULTIPLEXING_LOW
|
||||
|
||||
# anytls
|
||||
- name: anytls
|
||||
type: anytls
|
||||
server: 1.2.3.4
|
||||
port: 443
|
||||
password: "<your password>"
|
||||
# client-fingerprint: chrome
|
||||
udp: true
|
||||
# idle-session-check-interval: 30 # seconds
|
||||
# idle-session-timeout: 30 # seconds
|
||||
# min-idle-session: 0
|
||||
# sni: "example.com"
|
||||
# alpn:
|
||||
# - h2
|
||||
# - http/1.1
|
||||
# skip-cert-verify: true
|
||||
|
||||
# dns 出站会将请求劫持到内部 dns 模块,所有请求均在内部处理
|
||||
- name: "dns-out"
|
||||
type: dns
|
||||
@@ -1078,7 +1096,7 @@ sub-rules:
|
||||
listeners:
|
||||
- name: socks5-in-1
|
||||
type: socks
|
||||
port: 10808
|
||||
port: 10808 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
#listen: 0.0.0.0 # 默认监听 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理
|
||||
@@ -1086,20 +1104,26 @@ listeners:
|
||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
||||
- name: http-in-1
|
||||
type: http
|
||||
port: 10809
|
||||
port: 10809 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
||||
- name: mixed-in-1
|
||||
type: mixed # HTTP(S) 和 SOCKS 代理混合
|
||||
port: 10810
|
||||
port: 10810 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
@@ -1107,17 +1131,20 @@ listeners:
|
||||
# users: # 如果不填写users项,则遵从全局authentication设置,如果填写会忽略全局设置, 如想跳过该入站的验证可填写 users: []
|
||||
# - username: aaa
|
||||
# password: aaa
|
||||
# 下面两项如果填写则开启 tls(需要同时填写)
|
||||
# certificate: ./server.crt
|
||||
# private-key: ./server.key
|
||||
|
||||
- name: reidr-in-1
|
||||
type: redir
|
||||
port: 10811
|
||||
port: 10811 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
|
||||
- name: tproxy-in-1
|
||||
type: tproxy
|
||||
port: 10812
|
||||
port: 10812 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
@@ -1125,7 +1152,7 @@ listeners:
|
||||
|
||||
- name: shadowsocks-in-1
|
||||
type: shadowsocks
|
||||
port: 10813
|
||||
port: 10813 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
@@ -1134,7 +1161,7 @@ listeners:
|
||||
|
||||
- name: vmess-in-1
|
||||
type: vmess
|
||||
port: 10814
|
||||
port: 10814 # 支持使用ports格式,例如200,302 or 200,204,401-429,501-503
|
||||
listen: 0.0.0.0
|
||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
|
||||
@@ -1143,6 +1170,7 @@ 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
|
||||
@@ -1157,7 +1185,7 @@ listeners:
|
||||
|
||||
- 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 名称必须合法,否则会出错)
|
||||
@@ -1177,7 +1205,7 @@ 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 名称必须合法,否则会出错)
|
||||
@@ -1186,7 +1214,7 @@ listeners:
|
||||
|
||||
- name: vless-in-1
|
||||
type: vless
|
||||
port: 10817
|
||||
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 名称必须合法,否则会出错)
|
||||
@@ -1195,6 +1223,7 @@ listeners:
|
||||
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
|
||||
@@ -1208,6 +1237,46 @@ listeners:
|
||||
- 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
|
||||
|
||||
32
go.mod
32
go.mod
@@ -6,12 +6,12 @@ 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.11.1
|
||||
github.com/go-chi/chi/v5 v5.2.0
|
||||
github.com/dlclark/regexp2 v1.11.5
|
||||
github.com/enfein/mieru/v3 v3.11.2
|
||||
github.com/go-chi/chi/v5 v5.2.1
|
||||
github.com/go-chi/render v1.0.3
|
||||
github.com/gobwas/ws v1.4.0
|
||||
github.com/gofrs/uuid/v5 v5.3.0
|
||||
github.com/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
|
||||
@@ -19,15 +19,16 @@ require (
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
||||
github.com/metacubex/chacha v0.1.0
|
||||
github.com/metacubex/chacha v0.1.1
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||
github.com/metacubex/quic-go v0.48.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/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.5
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||
github.com/metacubex/utls v1.6.6
|
||||
@@ -40,10 +41,10 @@ require (
|
||||
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/reality v0.0.0-20230406110435-ee17307e7691
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing v0.5.2
|
||||
github.com/sagernet/sing-mux v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.1.5
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.1
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
@@ -53,10 +54,10 @@ require (
|
||||
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.32.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // lastest version compatible with golang1.20
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/sys v0.29.0
|
||||
golang.org/x/net v0.35.0
|
||||
golang.org/x/sys v0.30.0
|
||||
google.golang.org/protobuf v1.34.2 // lastest version compatible with golang1.20
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
lukechampine.com/blake3 v1.3.0
|
||||
@@ -98,7 +99,6 @@ require (
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/sagernet/nftables v0.3.0-beta.4 // indirect
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect
|
||||
github.com/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,10 +111,10 @@ require (
|
||||
gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/mod v0.20.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.24.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20241121030428-33b6ebc52000
|
||||
replace github.com/sagernet/sing => github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a
|
||||
|
||||
58
go.sum
58
go.sum
@@ -24,12 +24,12 @@ github.com/coreos/go-iptables v0.8.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFE
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
|
||||
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/enfein/mieru/v3 v3.11.1 h1:G6PjSWPvzvgWUoMqnD4sBe1OGkDgrpNVT2UqCQhE6SE=
|
||||
github.com/enfein/mieru/v3 v3.11.1/go.mod h1:XvVfNsM78lUMSlJJKXJZ0Hn3lAB2o/ETXTbb84x5egw=
|
||||
github.com/enfein/mieru/v3 v3.11.2 h1:06KyGbXiiGz2nSHLJDOOkztAVY3cRr3wBMOpYxPotTo=
|
||||
github.com/enfein/mieru/v3 v3.11.2/go.mod h1:XvVfNsM78lUMSlJJKXJZ0Hn3lAB2o/ETXTbb84x5egw=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9 h1:/5RkVc9Rc81XmMyVqawCiDyrBHZbLAZgTTCqou4mwj8=
|
||||
github.com/ericlagergren/aegis v0.0.0-20230312195928-b4ce538b56f9/go.mod h1:hkIFzoiIPZYxdFOOLyDho59b7SrDfo+w3h+yWdlg45I=
|
||||
github.com/ericlagergren/polyval v0.0.0-20220411101811-e25bc10ba391 h1:8j2RH289RJplhA6WfdaPqzg1MjH2K8wX5e0uhAxrw2g=
|
||||
@@ -43,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.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
|
||||
github.com/go-chi/chi/v5 v5.2.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=
|
||||
@@ -59,8 +59,8 @@ github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/gofrs/uuid/v5 v5.3.0 h1:m0mUMr+oVYUdxpMLgSYCZiXe7PuVPnI94+OMeVBNedk=
|
||||
github.com/gofrs/uuid/v5 v5.3.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/gofrs/uuid/v5 v5.3.1 h1:aPx49MwJbekCzOyhZDjJVb0hx3A0KLjlbLx6p2gY0p0=
|
||||
github.com/gofrs/uuid/v5 v5.3.1/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
@@ -99,18 +99,20 @@ github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab h1:Chbw+/31
|
||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab/go.mod h1:xVKK8jC5Sd3hfh7WjmCq+HorehIbrBijaUWmcuKjPcI=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.0 h1:tg9RSJ18NvL38cCWNyYH1eiG6qDCyyXIaTLQthon0sc=
|
||||
github.com/metacubex/chacha v0.1.0/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
github.com/metacubex/gvisor v0.0.0-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/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/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=
|
||||
@@ -119,8 +121,8 @@ github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhD
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.4.5 h1:kWSyQzuzHI40r50OFBczfWIDvMBMy1RIk+JsXeBPRB0=
|
||||
github.com/metacubex/sing-tun v0.4.5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3 h1:2kq6azIvsTjTnyw66xXDl5zMzIJqF7GTbvLpkroHssg=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250203033000-f61322b3dbe3/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
@@ -168,8 +170,6 @@ 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/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc=
|
||||
github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU=
|
||||
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=
|
||||
@@ -232,8 +232,8 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBs
|
||||
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
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 +242,11 @@ golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0=
|
||||
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
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.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.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=
|
||||
@@ -262,12 +262,12 @@ 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.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
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.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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=
|
||||
|
||||
184
listener/anytls/server.go
Normal file
184
listener/anytls/server.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||
|
||||
"github.com/sagernet/sing/common/auth"
|
||||
"github.com/sagernet/sing/common/bufio"
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.AnyTLSServer
|
||||
listeners []net.Listener
|
||||
tlsConfig *tls.Config
|
||||
userMap map[[32]byte]string
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
}
|
||||
|
||||
func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-ANYTLS"),
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
sl = &Listener{
|
||||
config: config,
|
||||
tlsConfig: tlsConfig,
|
||||
userMap: make(map[[32]byte]string),
|
||||
}
|
||||
|
||||
for user, password := range config.Users {
|
||||
sl.userMap[sha256.Sum256([]byte(password))] = user
|
||||
}
|
||||
|
||||
if len(config.PaddingScheme) > 0 {
|
||||
if !padding.UpdatePaddingScheme([]byte(config.PaddingScheme), &sl.padding) {
|
||||
return nil, errors.New("incorrect padding scheme format")
|
||||
}
|
||||
} else {
|
||||
padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &sl.padding)
|
||||
}
|
||||
|
||||
// Using sing handler can automatically handle UoT
|
||||
h, err := sing.NewListenerHandler(sing.ListenerConfig{
|
||||
Tunnel: tunnel,
|
||||
Type: C.ANYTLS,
|
||||
Additions: additions,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, addr := range strings.Split(config.Listen, ",") {
|
||||
addr := addr
|
||||
|
||||
//TCP
|
||||
l, err := inbound.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
c, err := l.Accept()
|
||||
if err != nil {
|
||||
if sl.closed {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
go sl.HandleConn(c, h)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return sl, nil
|
||||
}
|
||||
|
||||
func (l *Listener) Close() error {
|
||||
l.closed = true
|
||||
var retErr error
|
||||
for _, lis := range l.listeners {
|
||||
err := lis.Close()
|
||||
if err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
return retErr
|
||||
}
|
||||
|
||||
func (l *Listener) Config() string {
|
||||
return l.config.String()
|
||||
}
|
||||
|
||||
func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||
for _, lis := range l.listeners {
|
||||
addrList = append(addrList, lis.Addr())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
||||
ctx := context.TODO()
|
||||
|
||||
conn = tls.Server(conn, l.tlsConfig)
|
||||
defer conn.Close()
|
||||
|
||||
b := buf.NewPacket()
|
||||
defer b.Release()
|
||||
|
||||
_, err := b.ReadOnceFrom(conn)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
conn = bufio.NewCachedConn(conn, b)
|
||||
|
||||
by, err := b.ReadBytes(32)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var passwordSha256 [32]byte
|
||||
copy(passwordSha256[:], by)
|
||||
if user, ok := l.userMap[passwordSha256]; ok {
|
||||
ctx = auth.ContextWithUser(ctx, user)
|
||||
} else {
|
||||
return
|
||||
}
|
||||
by, err = b.ReadBytes(2)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
paddingLen := binary.BigEndian.Uint16(by)
|
||||
if paddingLen > 0 {
|
||||
_, err = b.ReadBytes(int(paddingLen))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
session := session.NewServerSession(conn, func(stream *session.Stream) {
|
||||
defer stream.Close()
|
||||
|
||||
destination, err := M.SocksaddrSerializer.ReadAddrPort(stream)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
h.NewConnection(ctx, stream, M.Metadata{
|
||||
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
Destination: destination,
|
||||
})
|
||||
}, &l.padding)
|
||||
session.Run()
|
||||
session.Close()
|
||||
}
|
||||
19
listener/config/anytls.go
Normal file
19
listener/config/anytls.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type AnyTLSServer struct {
|
||||
Enable bool `yaml:"enable" json:"enable"`
|
||||
Listen string `yaml:"listen" json:"listen"`
|
||||
Users map[string]string `yaml:"users" json:"users,omitempty"`
|
||||
Certificate string `yaml:"certificate" json:"certificate"`
|
||||
PrivateKey string `yaml:"private-key" json:"private-key"`
|
||||
PaddingScheme string `yaml:"padding-scheme" json:"padding-scheme,omitempty"`
|
||||
}
|
||||
|
||||
func (t AnyTLSServer) String() string {
|
||||
b, _ := json.Marshal(t)
|
||||
return string(b)
|
||||
}
|
||||
16
listener/config/auth.go
Normal file
16
listener/config/auth.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/metacubex/mihomo/component/auth"
|
||||
"github.com/metacubex/mihomo/listener/reality"
|
||||
)
|
||||
|
||||
// AuthServer for http/socks/mixed server
|
||||
type AuthServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
AuthStore auth.AuthStore
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -14,14 +14,15 @@ type VlessUser struct {
|
||||
}
|
||||
|
||||
type VlessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VlessUser
|
||||
WsPath string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
MuxOption sing.MuxOption `yaml:"mux-option" json:"mux-option,omitempty"`
|
||||
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 {
|
||||
|
||||
@@ -14,14 +14,15 @@ type VmessUser struct {
|
||||
}
|
||||
|
||||
type VmessServer struct {
|
||||
Enable bool
|
||||
Listen string
|
||||
Users []VmessUser
|
||||
WsPath string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
RealityConfig reality.Config
|
||||
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()
|
||||
|
||||
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"
|
||||
@@ -83,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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_vless"
|
||||
@@ -9,12 +11,13 @@ import (
|
||||
|
||||
type VlessOption struct {
|
||||
BaseOption
|
||||
Users []VlessUser `inbound:"users"`
|
||||
WsPath string `inbound:"ws-path,omitempty"`
|
||||
Certificate string `inbound:"certificate,omitempty"`
|
||||
PrivateKey string `inbound:"private-key,omitempty"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,omitempty"`
|
||||
MuxOption MuxOption `inbound:"mux-option,omitempty"`
|
||||
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 {
|
||||
@@ -51,14 +54,15 @@ func NewVless(options *VlessOption) (*Vless, error) {
|
||||
Base: base,
|
||||
config: options,
|
||||
vs: LC.VlessServer{
|
||||
Enable: true,
|
||||
Listen: base.RawAddress(),
|
||||
Users: users,
|
||||
WsPath: options.WsPath,
|
||||
Certificate: options.Certificate,
|
||||
PrivateKey: options.PrivateKey,
|
||||
RealityConfig: options.RealityConfig.Build(),
|
||||
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
|
||||
}
|
||||
@@ -70,25 +74,18 @@ func (v *Vless) Config() C.InboundConfig {
|
||||
|
||||
// Address implements constant.InboundListener
|
||||
func (v *Vless) 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 *Vless) Listen(tunnel C.Tunnel) error {
|
||||
var err error
|
||||
users := make([]LC.VlessUser, len(v.config.Users))
|
||||
for i, v := range v.config.Users {
|
||||
users[i] = LC.VlessUser{
|
||||
Username: v.Username,
|
||||
UUID: v.UUID,
|
||||
Flow: v.Flow,
|
||||
}
|
||||
}
|
||||
v.l, err = sing_vless.New(v.vs, tunnel, v.Additions()...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -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,12 +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"`
|
||||
RealityConfig RealityConfig `inbound:"reality-config,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 {
|
||||
@@ -51,14 +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,
|
||||
RealityConfig: options.RealityConfig.Build(),
|
||||
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
|
||||
}
|
||||
@@ -70,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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -93,6 +93,13 @@ func ParseListener(mapping map[string]any) (C.InboundListener, error) {
|
||||
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)
|
||||
@@ -113,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)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/metacubex/mihomo/listener/inner"
|
||||
"github.com/metacubex/mihomo/ntp"
|
||||
|
||||
"github.com/sagernet/reality"
|
||||
"github.com/metacubex/reality"
|
||||
)
|
||||
|
||||
type Conn = reality.Conn
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"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"
|
||||
@@ -92,7 +93,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpMux *http.ServeMux
|
||||
var httpHandler http.Handler
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||
@@ -111,17 +112,28 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -141,8 +153,8 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
sl.listeners = append(sl.listeners, l)
|
||||
|
||||
go func() {
|
||||
if httpMux != nil {
|
||||
_ = http.Serve(l, httpMux)
|
||||
if httpHandler != nil {
|
||||
_ = http.Serve(l, httpHandler)
|
||||
return
|
||||
}
|
||||
for {
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"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"
|
||||
@@ -76,7 +77,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
|
||||
tlsConfig := &tls.Config{}
|
||||
var realityBuilder *reality.Builder
|
||||
var httpMux *http.ServeMux
|
||||
var httpHandler http.Handler
|
||||
|
||||
if config.Certificate != "" && config.PrivateKey != "" {
|
||||
cert, err := N.ParseCert(config.Certificate, config.PrivateKey, C.Path)
|
||||
@@ -95,17 +96,28 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
}
|
||||
}
|
||||
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
|
||||
@@ -123,8 +135,8 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||
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()
|
||||
|
||||
43
listener/trojan/packet.go
Normal file
43
listener/trojan/packet.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package trojan
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
type packet struct {
|
||||
pc net.PacketConn
|
||||
rAddr net.Addr
|
||||
payload []byte
|
||||
put func()
|
||||
}
|
||||
|
||||
func (c *packet) Data() []byte {
|
||||
return c.payload
|
||||
}
|
||||
|
||||
// WriteBack wirtes UDP packet with source(ip, port) = `addr`
|
||||
func (c *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) {
|
||||
if addr == nil {
|
||||
err = errors.New("address is invalid")
|
||||
return
|
||||
}
|
||||
return c.pc.WriteTo(b, addr)
|
||||
}
|
||||
|
||||
// LocalAddr returns the source IP/Port of UDP Packet
|
||||
func (c *packet) LocalAddr() net.Addr {
|
||||
return c.rAddr
|
||||
}
|
||||
|
||||
func (c *packet) Drop() {
|
||||
if c.put != nil {
|
||||
c.put()
|
||||
c.put = nil
|
||||
}
|
||||
c.payload = nil
|
||||
}
|
||||
|
||||
func (c *packet) InAddr() net.Addr {
|
||||
return c.pc.LocalAddr()
|
||||
}
|
||||
283
listener/trojan/server.go
Normal file
283
listener/trojan/server.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package trojan
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/inbound"
|
||||
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/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
"github.com/metacubex/mihomo/transport/trojan"
|
||||
mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
"github.com/sagernet/smux"
|
||||
)
|
||||
|
||||
type Listener struct {
|
||||
closed bool
|
||||
config LC.TrojanServer
|
||||
listeners []net.Listener
|
||||
keys map[[trojan.KeyLength]byte]string
|
||||
pickCipher core.Cipher
|
||||
handler *sing.ListenerHandler
|
||||
}
|
||||
|
||||
func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition) (sl *Listener, err error) {
|
||||
if len(additions) == 0 {
|
||||
additions = []inbound.Addition{
|
||||
inbound.WithInName("DEFAULT-TROJAN"),
|
||||
inbound.WithSpecialRules(""),
|
||||
}
|
||||
}
|
||||
h, err := sing.NewListenerHandler(sing.ListenerConfig{
|
||||
Tunnel: tunnel,
|
||||
Type: C.TROJAN,
|
||||
Additions: additions,
|
||||
MuxOption: config.MuxOption,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keys := make(map[[trojan.KeyLength]byte]string)
|
||||
for _, user := range config.Users {
|
||||
keys[trojan.Key(user.Password)] = user.Username
|
||||
}
|
||||
|
||||
var pickCipher core.Cipher
|
||||
if config.TrojanSSOption.Enabled {
|
||||
if config.TrojanSSOption.Password == "" {
|
||||
return nil, errors.New("empty password")
|
||||
}
|
||||
if config.TrojanSSOption.Method == "" {
|
||||
config.TrojanSSOption.Method = "AES-128-GCM"
|
||||
}
|
||||
pickCipher, err = core.PickCipher(config.TrojanSSOption.Method, nil, config.TrojanSSOption.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
sl = &Listener{false, config, nil, keys, pickCipher, h}
|
||||
|
||||
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 if !config.TrojanSSOption.Enabled {
|
||||
return nil, errors.New("disallow using Trojan without both certificates/reality/ss 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, additions...)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
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) {
|
||||
defer conn.Close()
|
||||
|
||||
if l.pickCipher != nil {
|
||||
conn = l.pickCipher.StreamConn(conn)
|
||||
}
|
||||
|
||||
var key [trojan.KeyLength]byte
|
||||
if _, err := io.ReadFull(conn, key[:]); err != nil {
|
||||
//log.Warnln("read key error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if user, ok := l.keys[key]; ok {
|
||||
additions = append(additions, inbound.WithInUser(user))
|
||||
} else {
|
||||
//log.Warnln("no such key")
|
||||
return
|
||||
}
|
||||
|
||||
var crlf [2]byte
|
||||
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
|
||||
//log.Warnln("read crlf error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
l.handleConn(false, conn, tunnel, additions...)
|
||||
}
|
||||
|
||||
func (l *Listener) handleConn(inMux bool, conn net.Conn, tunnel C.Tunnel, additions ...inbound.Addition) {
|
||||
if inMux {
|
||||
defer conn.Close()
|
||||
}
|
||||
|
||||
command, err := socks5.ReadByte(conn)
|
||||
if err != nil {
|
||||
//log.Warnln("read command error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
switch command {
|
||||
case trojan.CommandTCP, trojan.CommandUDP, trojan.CommandMux:
|
||||
default:
|
||||
//log.Warnln("unknown command: %d", command)
|
||||
return
|
||||
}
|
||||
|
||||
target, err := socks5.ReadAddr0(conn)
|
||||
if err != nil {
|
||||
//log.Warnln("read target error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !inMux {
|
||||
var crlf [2]byte
|
||||
if _, err := io.ReadFull(conn, crlf[:]); err != nil {
|
||||
//log.Warnln("read crlf error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch command {
|
||||
case trojan.CommandTCP:
|
||||
//tunnel.HandleTCPConn(inbound.NewSocket(target, conn, C.TROJAN, additions...))
|
||||
l.handler.HandleSocket(target, conn, additions...)
|
||||
case trojan.CommandUDP:
|
||||
pc := trojan.NewPacketConn(conn)
|
||||
for {
|
||||
data, put, remoteAddr, err := pc.WaitReadFrom()
|
||||
if err != nil {
|
||||
if put != nil {
|
||||
put()
|
||||
}
|
||||
break
|
||||
}
|
||||
cPacket := &packet{
|
||||
pc: pc,
|
||||
rAddr: remoteAddr,
|
||||
payload: data,
|
||||
put: put,
|
||||
}
|
||||
|
||||
tunnel.HandleUDPPacket(inbound.NewPacket(target, cPacket, C.TROJAN, additions...))
|
||||
}
|
||||
case trojan.CommandMux:
|
||||
if inMux {
|
||||
//log.Warnln("invalid command: %d", command)
|
||||
return
|
||||
}
|
||||
smuxConfig := smux.DefaultConfig()
|
||||
smuxConfig.KeepAliveDisabled = true
|
||||
session, err := smux.Server(conn, smuxConfig)
|
||||
if err != nil {
|
||||
//log.Warnln("smux server error: %s", err.Error())
|
||||
return
|
||||
}
|
||||
defer session.Close()
|
||||
for {
|
||||
stream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go l.handleConn(true, stream, tunnel, additions...)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package tuic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"strings"
|
||||
@@ -93,23 +92,14 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
||||
quicConfig.MaxDatagramFrameSize = int64(maxDatagramFrameSize)
|
||||
|
||||
handleTcpFn := func(conn net.Conn, addr socks5.Addr, _additions ...inbound.Addition) error {
|
||||
newAdditions := additions
|
||||
if len(_additions) > 0 {
|
||||
newAdditions = slices.Clone(additions)
|
||||
newAdditions = append(newAdditions, _additions...)
|
||||
}
|
||||
conn, metadata := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||
if h.IsSpecialFqdn(metadata.Host) {
|
||||
go func() { // ParseSpecialFqdn will block, so open a new goroutine
|
||||
_ = h.ParseSpecialFqdn(
|
||||
sing.WithAdditions(context.Background(), newAdditions...),
|
||||
conn,
|
||||
sing.ConvertMetadata(metadata),
|
||||
)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
go tunnel.HandleTCPConn(conn, metadata)
|
||||
//newAdditions := additions
|
||||
//if len(_additions) > 0 {
|
||||
// newAdditions = slices.Clone(additions)
|
||||
// newAdditions = append(newAdditions, _additions...)
|
||||
//}
|
||||
//conn, metadata := inbound.NewSocket(addr, conn, C.TUIC, newAdditions...)
|
||||
//go tunnel.HandleTCPConn(conn, metadata)
|
||||
go h.HandleSocket(addr, conn, _additions...) // h.HandleSocket will block, so open a new goroutine
|
||||
return nil
|
||||
}
|
||||
handleUdpFn := func(addr socks5.Addr, packet C.UDPPacket, _additions ...inbound.Addition) error {
|
||||
|
||||
@@ -81,8 +81,10 @@ func ParseRule(tp, payload, target string, params []string, subRules map[string]
|
||||
case "MATCH":
|
||||
parsed = RC.NewMatch(target)
|
||||
parseErr = nil
|
||||
case "":
|
||||
parseErr = fmt.Errorf("missing subsequent parameters: %s", payload)
|
||||
default:
|
||||
parseErr = fmt.Errorf("unsupported rule type %s", tp)
|
||||
parseErr = fmt.Errorf("unsupported rule type: %s", tp)
|
||||
}
|
||||
|
||||
if parseErr != nil {
|
||||
|
||||
@@ -59,7 +59,7 @@ func (c *classicalStrategy) Insert(rule string) {
|
||||
|
||||
r, err := c.parse(ruleType, rule, "", params)
|
||||
if err != nil {
|
||||
log.Warnln("parse rule error:[%s]", err.Error())
|
||||
log.Warnln("parse classical rule error: %s", err.Error())
|
||||
} else {
|
||||
if r.ShouldResolveIP() {
|
||||
c.shouldResolveIP = true
|
||||
@@ -83,7 +83,7 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
return item[0], item[1], nil
|
||||
} else if len(item) > 2 {
|
||||
if item[0] == "NOT" || item[0] == "OR" || item[0] == "AND" || item[0] == "SUB-RULE" || item[0] == "DOMAIN-REGEX" || item[0] == "PROCESS-NAME-REGEX" || item[0] == "PROCESS-PATH-REGEX" {
|
||||
return item[0], strings.Join(item[1:len(item)], ","), nil
|
||||
return item[0], strings.Join(item[1:], ","), nil
|
||||
} else {
|
||||
return item[0], item[1], item[2:]
|
||||
}
|
||||
@@ -95,8 +95,8 @@ func ruleParse(ruleRaw string) (string, string, []string) {
|
||||
func NewClassicalStrategy(parse func(tp, payload, target string, params []string, subRules map[string][]C.Rule) (parsed C.Rule, parseErr error)) *classicalStrategy {
|
||||
return &classicalStrategy{rules: []C.Rule{}, parse: func(tp, payload, target string, params []string) (parsed C.Rule, parseErr error) {
|
||||
switch tp {
|
||||
case "MATCH":
|
||||
return nil, fmt.Errorf("unsupported rule type on rule-set")
|
||||
case "MATCH", "RULE-SET", "SUB-RULE":
|
||||
return nil, fmt.Errorf("unsupported rule type on classical rule-set: %s", tp)
|
||||
default:
|
||||
return parse(tp, payload, target, params, nil)
|
||||
}
|
||||
|
||||
107
transport/anytls/client.go
Normal file
107
transport/anytls/client.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package anytls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
M "github.com/sagernet/sing/common/metadata"
|
||||
N "github.com/sagernet/sing/common/network"
|
||||
)
|
||||
|
||||
type ClientConfig struct {
|
||||
Password string
|
||||
IdleSessionCheckInterval time.Duration
|
||||
IdleSessionTimeout time.Duration
|
||||
MinIdleSession int
|
||||
Server M.Socksaddr
|
||||
Dialer N.Dialer
|
||||
TLSConfig *vmess.TLSConfig
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
passwordSha256 []byte
|
||||
tlsConfig *vmess.TLSConfig
|
||||
dialer N.Dialer
|
||||
server M.Socksaddr
|
||||
sessionClient *session.Client
|
||||
padding atomic.TypedValue[*padding.PaddingFactory]
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, config ClientConfig) *Client {
|
||||
pw := sha256.Sum256([]byte(config.Password))
|
||||
c := &Client{
|
||||
passwordSha256: pw[:],
|
||||
tlsConfig: config.TLSConfig,
|
||||
dialer: config.Dialer,
|
||||
server: config.Server,
|
||||
}
|
||||
// Initialize the padding state of this client
|
||||
padding.UpdatePaddingScheme(padding.DefaultPaddingScheme, &c.padding)
|
||||
c.sessionClient = session.NewClient(ctx, c.CreateOutboundTLSConnection, &c.padding, config.IdleSessionCheckInterval, config.IdleSessionTimeout, config.MinIdleSession)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) CreateProxy(ctx context.Context, destination M.Socksaddr) (net.Conn, error) {
|
||||
conn, err := c.sessionClient.CreateStream(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = M.SocksaddrSerializer.WriteAddrPort(conn, destination)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, error) {
|
||||
conn, err := c.dialer.DialContext(ctx, N.NetworkTCP, c.server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b := buf.NewPacket()
|
||||
defer b.Release()
|
||||
|
||||
b.Write(c.passwordSha256)
|
||||
var paddingLen int
|
||||
if pad := c.padding.Load().GenerateRecordPayloadSizes(0); len(pad) > 0 {
|
||||
paddingLen = pad[0]
|
||||
}
|
||||
binary.BigEndian.PutUint16(b.Extend(2), uint16(paddingLen))
|
||||
if paddingLen > 0 {
|
||||
b.WriteZeroN(paddingLen)
|
||||
}
|
||||
|
||||
getTlsConn := func() (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
|
||||
}
|
||||
tlsConn, err := getTlsConn()
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = b.WriteTo(tlsConn)
|
||||
if err != nil {
|
||||
tlsConn.Close()
|
||||
return nil, err
|
||||
}
|
||||
return tlsConn, nil
|
||||
}
|
||||
|
||||
func (h *Client) Close() error {
|
||||
return h.sessionClient.Close()
|
||||
}
|
||||
92
transport/anytls/padding/padding.go
Normal file
92
transport/anytls/padding/padding.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package padding
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/transport/anytls/util"
|
||||
)
|
||||
|
||||
const CheckMark = -1
|
||||
|
||||
var DefaultPaddingScheme = []byte(`stop=8
|
||||
0=34-120
|
||||
1=100-400
|
||||
2=400-500,c,500-1000,c,400-500,c,500-1000,c,500-1000,c,400-500
|
||||
3=500-1000
|
||||
4=500-1000
|
||||
5=500-1000
|
||||
6=500-1000
|
||||
7=500-1000`)
|
||||
|
||||
type PaddingFactory struct {
|
||||
scheme util.StringMap
|
||||
RawScheme []byte
|
||||
Stop uint32
|
||||
Md5 string
|
||||
}
|
||||
|
||||
func UpdatePaddingScheme(rawScheme []byte, to *atomic.TypedValue[*PaddingFactory]) bool {
|
||||
if p := NewPaddingFactory(rawScheme); p != nil {
|
||||
to.Store(p)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func NewPaddingFactory(rawScheme []byte) *PaddingFactory {
|
||||
p := &PaddingFactory{
|
||||
RawScheme: rawScheme,
|
||||
Md5: fmt.Sprintf("%x", md5.Sum(rawScheme)),
|
||||
}
|
||||
scheme := util.StringMapFromBytes(rawScheme)
|
||||
if len(scheme) == 0 {
|
||||
return nil
|
||||
}
|
||||
if stop, err := strconv.Atoi(scheme["stop"]); err == nil {
|
||||
p.Stop = uint32(stop)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
p.scheme = scheme
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *PaddingFactory) GenerateRecordPayloadSizes(pkt uint32) (pktSizes []int) {
|
||||
if s, ok := p.scheme[strconv.Itoa(int(pkt))]; ok {
|
||||
sRanges := strings.Split(s, ",")
|
||||
for _, sRange := range sRanges {
|
||||
sRangeMinMax := strings.Split(sRange, "-")
|
||||
if len(sRangeMinMax) == 2 {
|
||||
_min, err := strconv.ParseInt(sRangeMinMax[0], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_max, err := strconv.ParseInt(sRangeMinMax[1], 10, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if _min > _max {
|
||||
_min, _max = _max, _min
|
||||
}
|
||||
if _min <= 0 || _max <= 0 {
|
||||
continue
|
||||
}
|
||||
if _min == _max {
|
||||
pktSizes = append(pktSizes, int(_min))
|
||||
} else {
|
||||
i, _ := rand.Int(rand.Reader, big.NewInt(_max-_min))
|
||||
pktSizes = append(pktSizes, int(i.Int64()+_min))
|
||||
}
|
||||
} else if sRange == "c" {
|
||||
pktSizes = append(pktSizes, CheckMark)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
74
transport/anytls/pipe/deadline.go
Normal file
74
transport/anytls/pipe/deadline.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// PipeDeadline is an abstraction for handling timeouts.
|
||||
type PipeDeadline struct {
|
||||
mu sync.Mutex // Guards timer and cancel
|
||||
timer *time.Timer
|
||||
cancel chan struct{} // Must be non-nil
|
||||
}
|
||||
|
||||
func MakePipeDeadline() PipeDeadline {
|
||||
return PipeDeadline{cancel: make(chan struct{})}
|
||||
}
|
||||
|
||||
// Set sets the point in time when the deadline will time out.
|
||||
// A timeout event is signaled by closing the channel returned by waiter.
|
||||
// Once a timeout has occurred, the deadline can be refreshed by specifying a
|
||||
// t value in the future.
|
||||
//
|
||||
// A zero value for t prevents timeout.
|
||||
func (d *PipeDeadline) Set(t time.Time) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
if d.timer != nil && !d.timer.Stop() {
|
||||
<-d.cancel // Wait for the timer callback to finish and close cancel
|
||||
}
|
||||
d.timer = nil
|
||||
|
||||
// Time is zero, then there is no deadline.
|
||||
closed := isClosedChan(d.cancel)
|
||||
if t.IsZero() {
|
||||
if closed {
|
||||
d.cancel = make(chan struct{})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Time in the future, setup a timer to cancel in the future.
|
||||
if dur := time.Until(t); dur > 0 {
|
||||
if closed {
|
||||
d.cancel = make(chan struct{})
|
||||
}
|
||||
d.timer = time.AfterFunc(dur, func() {
|
||||
close(d.cancel)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Time in the past, so close immediately.
|
||||
if !closed {
|
||||
close(d.cancel)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait returns a channel that is closed when the deadline is exceeded.
|
||||
func (d *PipeDeadline) Wait() chan struct{} {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
return d.cancel
|
||||
}
|
||||
|
||||
func isClosedChan(c <-chan struct{}) bool {
|
||||
select {
|
||||
case <-c:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
232
transport/anytls/pipe/io_pipe.go
Normal file
232
transport/anytls/pipe/io_pipe.go
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Pipe adapter to connect code expecting an io.Reader
|
||||
// with code expecting an io.Writer.
|
||||
|
||||
package pipe
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// onceError is an object that will only store an error once.
|
||||
type onceError struct {
|
||||
sync.Mutex // guards following
|
||||
err error
|
||||
}
|
||||
|
||||
func (a *onceError) Store(err error) {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
if a.err != nil {
|
||||
return
|
||||
}
|
||||
a.err = err
|
||||
}
|
||||
func (a *onceError) Load() error {
|
||||
a.Lock()
|
||||
defer a.Unlock()
|
||||
return a.err
|
||||
}
|
||||
|
||||
// A pipe is the shared pipe structure underlying PipeReader and PipeWriter.
|
||||
type pipe struct {
|
||||
wrMu sync.Mutex // Serializes Write operations
|
||||
wrCh chan []byte
|
||||
rdCh chan int
|
||||
|
||||
once sync.Once // Protects closing done
|
||||
done chan struct{}
|
||||
rerr onceError
|
||||
werr onceError
|
||||
|
||||
readDeadline PipeDeadline
|
||||
writeDeadline PipeDeadline
|
||||
}
|
||||
|
||||
func (p *pipe) read(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-p.done:
|
||||
return 0, p.readCloseError()
|
||||
case <-p.readDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
default:
|
||||
}
|
||||
|
||||
select {
|
||||
case bw := <-p.wrCh:
|
||||
nr := copy(b, bw)
|
||||
p.rdCh <- nr
|
||||
return nr, nil
|
||||
case <-p.done:
|
||||
return 0, p.readCloseError()
|
||||
case <-p.readDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
|
||||
func (p *pipe) closeRead(err error) error {
|
||||
if err == nil {
|
||||
err = io.ErrClosedPipe
|
||||
}
|
||||
p.rerr.Store(err)
|
||||
p.once.Do(func() { close(p.done) })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *pipe) write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-p.done:
|
||||
return 0, p.writeCloseError()
|
||||
case <-p.writeDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
default:
|
||||
p.wrMu.Lock()
|
||||
defer p.wrMu.Unlock()
|
||||
}
|
||||
|
||||
for once := true; once || len(b) > 0; once = false {
|
||||
select {
|
||||
case p.wrCh <- b:
|
||||
nw := <-p.rdCh
|
||||
b = b[nw:]
|
||||
n += nw
|
||||
case <-p.done:
|
||||
return n, p.writeCloseError()
|
||||
case <-p.writeDeadline.Wait():
|
||||
return n, os.ErrDeadlineExceeded
|
||||
}
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (p *pipe) closeWrite(err error) error {
|
||||
if err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
p.werr.Store(err)
|
||||
p.once.Do(func() { close(p.done) })
|
||||
return nil
|
||||
}
|
||||
|
||||
// readCloseError is considered internal to the pipe type.
|
||||
func (p *pipe) readCloseError() error {
|
||||
rerr := p.rerr.Load()
|
||||
if werr := p.werr.Load(); rerr == nil && werr != nil {
|
||||
return werr
|
||||
}
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
|
||||
// writeCloseError is considered internal to the pipe type.
|
||||
func (p *pipe) writeCloseError() error {
|
||||
werr := p.werr.Load()
|
||||
if rerr := p.rerr.Load(); werr == nil && rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
|
||||
// A PipeReader is the read half of a pipe.
|
||||
type PipeReader struct{ pipe }
|
||||
|
||||
// Read implements the standard Read interface:
|
||||
// it reads data from the pipe, blocking until a writer
|
||||
// arrives or the write end is closed.
|
||||
// If the write end is closed with an error, that error is
|
||||
// returned as err; otherwise err is EOF.
|
||||
func (r *PipeReader) Read(data []byte) (n int, err error) {
|
||||
return r.pipe.read(data)
|
||||
}
|
||||
|
||||
// Close closes the reader; subsequent writes to the
|
||||
// write half of the pipe will return the error [ErrClosedPipe].
|
||||
func (r *PipeReader) Close() error {
|
||||
return r.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// CloseWithError closes the reader; subsequent writes
|
||||
// to the write half of the pipe will return the error err.
|
||||
//
|
||||
// CloseWithError never overwrites the previous error if it exists
|
||||
// and always returns nil.
|
||||
func (r *PipeReader) CloseWithError(err error) error {
|
||||
return r.pipe.closeRead(err)
|
||||
}
|
||||
|
||||
// A PipeWriter is the write half of a pipe.
|
||||
type PipeWriter struct{ r PipeReader }
|
||||
|
||||
// Write implements the standard Write interface:
|
||||
// it writes data to the pipe, blocking until one or more readers
|
||||
// have consumed all the data or the read end is closed.
|
||||
// If the read end is closed with an error, that err is
|
||||
// returned as err; otherwise err is [ErrClosedPipe].
|
||||
func (w *PipeWriter) Write(data []byte) (n int, err error) {
|
||||
return w.r.pipe.write(data)
|
||||
}
|
||||
|
||||
// Close closes the writer; subsequent reads from the
|
||||
// read half of the pipe will return no bytes and EOF.
|
||||
func (w *PipeWriter) Close() error {
|
||||
return w.CloseWithError(nil)
|
||||
}
|
||||
|
||||
// CloseWithError closes the writer; subsequent reads from the
|
||||
// read half of the pipe will return no bytes and the error err,
|
||||
// or EOF if err is nil.
|
||||
//
|
||||
// CloseWithError never overwrites the previous error if it exists
|
||||
// and always returns nil.
|
||||
func (w *PipeWriter) CloseWithError(err error) error {
|
||||
return w.r.pipe.closeWrite(err)
|
||||
}
|
||||
|
||||
// Pipe creates a synchronous in-memory pipe.
|
||||
// It can be used to connect code expecting an [io.Reader]
|
||||
// with code expecting an [io.Writer].
|
||||
//
|
||||
// Reads and Writes on the pipe are matched one to one
|
||||
// except when multiple Reads are needed to consume a single Write.
|
||||
// That is, each Write to the [PipeWriter] blocks until it has satisfied
|
||||
// one or more Reads from the [PipeReader] that fully consume
|
||||
// the written data.
|
||||
// The data is copied directly from the Write to the corresponding
|
||||
// Read (or Reads); there is no internal buffering.
|
||||
//
|
||||
// It is safe to call Read and Write in parallel with each other or with Close.
|
||||
// Parallel calls to Read and parallel calls to Write are also safe:
|
||||
// the individual calls will be gated sequentially.
|
||||
//
|
||||
// Added SetReadDeadline and SetWriteDeadline methods based on `io.Pipe`.
|
||||
func Pipe() (*PipeReader, *PipeWriter) {
|
||||
pw := &PipeWriter{r: PipeReader{pipe: pipe{
|
||||
wrCh: make(chan []byte),
|
||||
rdCh: make(chan int),
|
||||
done: make(chan struct{}),
|
||||
readDeadline: MakePipeDeadline(),
|
||||
writeDeadline: MakePipeDeadline(),
|
||||
}}}
|
||||
return &pw.r, pw
|
||||
}
|
||||
|
||||
func (p *PipeReader) SetReadDeadline(t time.Time) error {
|
||||
if isClosedChan(p.done) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.readDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PipeWriter) SetWriteDeadline(t time.Time) error {
|
||||
if isClosedChan(p.r.done) {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
p.r.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
205
transport/anytls/session/client.go
Normal file
205
transport/anytls/session/client.go
Normal file
@@ -0,0 +1,205 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/skiplist"
|
||||
"github.com/metacubex/mihomo/transport/anytls/util"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
die context.Context
|
||||
dieCancel context.CancelFunc
|
||||
|
||||
dialOut util.DialOutFunc
|
||||
|
||||
sessionCounter atomic.Uint64
|
||||
|
||||
idleSession *skiplist.SkipList[uint64, *Session]
|
||||
idleSessionLock sync.Mutex
|
||||
|
||||
sessions map[uint64]*Session
|
||||
sessionsLock sync.Mutex
|
||||
|
||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||
|
||||
idleSessionTimeout time.Duration
|
||||
minIdleSession int
|
||||
}
|
||||
|
||||
func NewClient(ctx context.Context, dialOut util.DialOutFunc, _padding *atomic.TypedValue[*padding.PaddingFactory], idleSessionCheckInterval, idleSessionTimeout time.Duration, minIdleSession int) *Client {
|
||||
c := &Client{
|
||||
sessions: make(map[uint64]*Session),
|
||||
dialOut: dialOut,
|
||||
padding: _padding,
|
||||
idleSessionTimeout: idleSessionTimeout,
|
||||
minIdleSession: minIdleSession,
|
||||
}
|
||||
if idleSessionCheckInterval <= time.Second*5 {
|
||||
idleSessionCheckInterval = time.Second * 30
|
||||
}
|
||||
if c.idleSessionTimeout <= time.Second*5 {
|
||||
c.idleSessionTimeout = time.Second * 30
|
||||
}
|
||||
c.die, c.dieCancel = context.WithCancel(ctx)
|
||||
c.idleSession = skiplist.NewSkipList[uint64, *Session]()
|
||||
util.StartRoutine(c.die, idleSessionCheckInterval, c.idleCleanup)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Client) CreateStream(ctx context.Context) (net.Conn, error) {
|
||||
select {
|
||||
case <-c.die.Done():
|
||||
return nil, io.ErrClosedPipe
|
||||
default:
|
||||
}
|
||||
|
||||
var session *Session
|
||||
var stream *Stream
|
||||
var err error
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
session, err = c.findSession(ctx)
|
||||
if session == nil {
|
||||
return nil, fmt.Errorf("failed to create session: %w", err)
|
||||
}
|
||||
stream, err = session.OpenStream()
|
||||
if err != nil {
|
||||
_ = session.Close()
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if session == nil || stream == nil {
|
||||
return nil, fmt.Errorf("too many closed session: %w", err)
|
||||
}
|
||||
|
||||
stream.dieHook = func() {
|
||||
if session.IsClosed() {
|
||||
if session.dieHook != nil {
|
||||
session.dieHook()
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case <-c.die.Done():
|
||||
// Now client has been closed
|
||||
go session.Close()
|
||||
default:
|
||||
c.idleSessionLock.Lock()
|
||||
session.idleSince = time.Now()
|
||||
c.idleSession.Insert(math.MaxUint64-session.seq, session)
|
||||
c.idleSessionLock.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
func (c *Client) findSession(ctx context.Context) (*Session, error) {
|
||||
var idle *Session
|
||||
|
||||
c.idleSessionLock.Lock()
|
||||
if !c.idleSession.IsEmpty() {
|
||||
it := c.idleSession.Iterate()
|
||||
idle = it.Value()
|
||||
c.idleSession.Remove(it.Key())
|
||||
}
|
||||
c.idleSessionLock.Unlock()
|
||||
|
||||
if idle == nil {
|
||||
s, err := c.createSession(ctx)
|
||||
return s, err
|
||||
}
|
||||
return idle, nil
|
||||
}
|
||||
|
||||
func (c *Client) createSession(ctx context.Context) (*Session, error) {
|
||||
underlying, err := c.dialOut(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session := NewClientSession(underlying, c.padding)
|
||||
session.seq = c.sessionCounter.Add(1)
|
||||
session.dieHook = func() {
|
||||
//logrus.Debugln("session died", session)
|
||||
c.idleSessionLock.Lock()
|
||||
c.idleSession.Remove(math.MaxUint64 - session.seq)
|
||||
c.idleSessionLock.Unlock()
|
||||
|
||||
c.sessionsLock.Lock()
|
||||
delete(c.sessions, session.seq)
|
||||
c.sessionsLock.Unlock()
|
||||
}
|
||||
|
||||
c.sessionsLock.Lock()
|
||||
c.sessions[session.seq] = session
|
||||
c.sessionsLock.Unlock()
|
||||
|
||||
session.Run()
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func (c *Client) Close() error {
|
||||
c.dieCancel()
|
||||
|
||||
c.sessionsLock.Lock()
|
||||
sessionToClose := make([]*Session, 0, len(c.sessions))
|
||||
for seq, session := range c.sessions {
|
||||
sessionToClose = append(sessionToClose, session)
|
||||
delete(c.sessions, seq)
|
||||
}
|
||||
c.sessionsLock.Unlock()
|
||||
|
||||
for _, session := range sessionToClose {
|
||||
session.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) idleCleanup() {
|
||||
c.idleCleanupExpTime(time.Now().Add(-c.idleSessionTimeout))
|
||||
}
|
||||
|
||||
func (c *Client) idleCleanupExpTime(expTime time.Time) {
|
||||
sessionToRemove := make([]*Session, 0, c.idleSession.Len())
|
||||
|
||||
c.idleSessionLock.Lock()
|
||||
it := c.idleSession.Iterate()
|
||||
|
||||
activeCount := 0
|
||||
for it.IsNotEnd() {
|
||||
session := it.Value()
|
||||
key := it.Key()
|
||||
it.MoveToNext()
|
||||
|
||||
if !session.idleSince.Before(expTime) {
|
||||
activeCount++
|
||||
continue
|
||||
}
|
||||
|
||||
if activeCount < c.minIdleSession {
|
||||
session.idleSince = time.Now()
|
||||
activeCount++
|
||||
continue
|
||||
}
|
||||
|
||||
sessionToRemove = append(sessionToRemove, session)
|
||||
c.idleSession.Remove(key)
|
||||
}
|
||||
c.idleSessionLock.Unlock()
|
||||
|
||||
for _, session := range sessionToRemove {
|
||||
session.Close()
|
||||
}
|
||||
}
|
||||
44
transport/anytls/session/frame.go
Normal file
44
transport/anytls/session/frame.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
)
|
||||
|
||||
const ( // cmds
|
||||
cmdWaste = 0 // Paddings
|
||||
cmdSYN = 1 // stream open
|
||||
cmdPSH = 2 // data push
|
||||
cmdFIN = 3 // stream close, a.k.a EOF mark
|
||||
cmdSettings = 4 // Settings
|
||||
cmdAlert = 5 // Alert
|
||||
cmdUpdatePaddingScheme = 6 // update padding scheme
|
||||
)
|
||||
|
||||
const (
|
||||
headerOverHeadSize = 1 + 4 + 2
|
||||
)
|
||||
|
||||
// frame defines a packet from or to be multiplexed into a single connection
|
||||
type frame struct {
|
||||
cmd byte // 1
|
||||
sid uint32 // 4
|
||||
data []byte // 2 + len(data)
|
||||
}
|
||||
|
||||
func newFrame(cmd byte, sid uint32) frame {
|
||||
return frame{cmd: cmd, sid: sid}
|
||||
}
|
||||
|
||||
type rawHeader [headerOverHeadSize]byte
|
||||
|
||||
func (h rawHeader) Cmd() byte {
|
||||
return h[0]
|
||||
}
|
||||
|
||||
func (h rawHeader) StreamID() uint32 {
|
||||
return binary.BigEndian.Uint32(h[1:])
|
||||
}
|
||||
|
||||
func (h rawHeader) Length() uint16 {
|
||||
return binary.BigEndian.Uint16(h[5:])
|
||||
}
|
||||
383
transport/anytls/session/session.go
Normal file
383
transport/anytls/session/session.go
Normal file
@@ -0,0 +1,383 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"net"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
"github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/util"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
conn net.Conn
|
||||
connLock sync.Mutex
|
||||
|
||||
streams map[uint32]*Stream
|
||||
streamId atomic.Uint32
|
||||
streamLock sync.RWMutex
|
||||
|
||||
dieOnce sync.Once
|
||||
die chan struct{}
|
||||
dieHook func()
|
||||
|
||||
// pool
|
||||
seq uint64
|
||||
idleSince time.Time
|
||||
padding *atomic.TypedValue[*padding.PaddingFactory]
|
||||
|
||||
// client
|
||||
isClient bool
|
||||
sendPadding bool
|
||||
buffering bool
|
||||
buffer []byte
|
||||
pktCounter atomic.Uint32
|
||||
|
||||
// server
|
||||
onNewStream func(stream *Stream)
|
||||
}
|
||||
|
||||
func NewClientSession(conn net.Conn, _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
||||
s := &Session{
|
||||
conn: conn,
|
||||
isClient: true,
|
||||
sendPadding: true,
|
||||
padding: _padding,
|
||||
}
|
||||
s.die = make(chan struct{})
|
||||
s.streams = make(map[uint32]*Stream)
|
||||
return s
|
||||
}
|
||||
|
||||
func NewServerSession(conn net.Conn, onNewStream func(stream *Stream), _padding *atomic.TypedValue[*padding.PaddingFactory]) *Session {
|
||||
s := &Session{
|
||||
conn: conn,
|
||||
onNewStream: onNewStream,
|
||||
padding: _padding,
|
||||
}
|
||||
s.die = make(chan struct{})
|
||||
s.streams = make(map[uint32]*Stream)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Session) Run() {
|
||||
if !s.isClient {
|
||||
s.recvLoop()
|
||||
return
|
||||
}
|
||||
|
||||
settings := util.StringMap{
|
||||
"v": "1",
|
||||
"client": "mihomo/" + constant.Version,
|
||||
"padding-md5": s.padding.Load().Md5,
|
||||
}
|
||||
f := newFrame(cmdSettings, 0)
|
||||
f.data = settings.ToBytes()
|
||||
s.buffering = true
|
||||
s.writeFrame(f)
|
||||
|
||||
go s.recvLoop()
|
||||
}
|
||||
|
||||
// IsClosed does a safe check to see if we have shutdown
|
||||
func (s *Session) IsClosed() bool {
|
||||
select {
|
||||
case <-s.die:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Close is used to close the session and all streams.
|
||||
func (s *Session) Close() error {
|
||||
var once bool
|
||||
s.dieOnce.Do(func() {
|
||||
close(s.die)
|
||||
once = true
|
||||
})
|
||||
|
||||
if once {
|
||||
if s.dieHook != nil {
|
||||
s.dieHook()
|
||||
}
|
||||
s.streamLock.Lock()
|
||||
for k := range s.streams {
|
||||
s.streams[k].sessionClose()
|
||||
}
|
||||
s.streamLock.Unlock()
|
||||
return s.conn.Close()
|
||||
} else {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// OpenStream is used to create a new stream for CLIENT
|
||||
func (s *Session) OpenStream() (*Stream, error) {
|
||||
if s.IsClosed() {
|
||||
return nil, io.ErrClosedPipe
|
||||
}
|
||||
|
||||
sid := s.streamId.Add(1)
|
||||
stream := newStream(sid, s)
|
||||
|
||||
//logrus.Debugln("stream open", sid, s.streams)
|
||||
|
||||
if _, err := s.writeFrame(newFrame(cmdSYN, sid)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.buffering = false // proxy Write it's SocksAddr to flush the buffer
|
||||
|
||||
s.streamLock.Lock()
|
||||
defer s.streamLock.Unlock()
|
||||
select {
|
||||
case <-s.die:
|
||||
return nil, io.ErrClosedPipe
|
||||
default:
|
||||
s.streams[sid] = stream
|
||||
return stream, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Session) recvLoop() error {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorln("[BUG] %v %s", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
defer s.Close()
|
||||
|
||||
var receivedSettingsFromClient bool
|
||||
var hdr rawHeader
|
||||
|
||||
for {
|
||||
if s.IsClosed() {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
// read header first
|
||||
if _, err := io.ReadFull(s.conn, hdr[:]); err == nil {
|
||||
sid := hdr.StreamID()
|
||||
switch hdr.Cmd() {
|
||||
case cmdPSH:
|
||||
if hdr.Length() > 0 {
|
||||
buffer := pool.Get(int(hdr.Length()))
|
||||
if _, err := io.ReadFull(s.conn, buffer); err == nil {
|
||||
s.streamLock.RLock()
|
||||
stream, ok := s.streams[sid]
|
||||
s.streamLock.RUnlock()
|
||||
if ok {
|
||||
stream.pipeW.Write(buffer)
|
||||
}
|
||||
pool.Put(buffer)
|
||||
} else {
|
||||
pool.Put(buffer)
|
||||
return err
|
||||
}
|
||||
}
|
||||
case cmdSYN: // should be server only
|
||||
if !s.isClient && !receivedSettingsFromClient {
|
||||
f := newFrame(cmdAlert, 0)
|
||||
f.data = []byte("client did not send its settings")
|
||||
s.writeFrame(f)
|
||||
return nil
|
||||
}
|
||||
s.streamLock.Lock()
|
||||
if _, ok := s.streams[sid]; !ok {
|
||||
stream := newStream(sid, s)
|
||||
s.streams[sid] = stream
|
||||
if s.onNewStream != nil {
|
||||
go s.onNewStream(stream)
|
||||
} else {
|
||||
go s.Close()
|
||||
}
|
||||
}
|
||||
s.streamLock.Unlock()
|
||||
case cmdFIN:
|
||||
s.streamLock.RLock()
|
||||
stream, ok := s.streams[sid]
|
||||
s.streamLock.RUnlock()
|
||||
if ok {
|
||||
stream.Close()
|
||||
}
|
||||
//logrus.Debugln("stream fin", sid, s.streams)
|
||||
case cmdWaste:
|
||||
if hdr.Length() > 0 {
|
||||
buffer := pool.Get(int(hdr.Length()))
|
||||
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||
pool.Put(buffer)
|
||||
return err
|
||||
}
|
||||
pool.Put(buffer)
|
||||
}
|
||||
case cmdSettings:
|
||||
if hdr.Length() > 0 {
|
||||
buffer := pool.Get(int(hdr.Length()))
|
||||
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||
pool.Put(buffer)
|
||||
return err
|
||||
}
|
||||
if !s.isClient {
|
||||
receivedSettingsFromClient = true
|
||||
m := util.StringMapFromBytes(buffer)
|
||||
paddingF := s.padding.Load()
|
||||
if m["padding-md5"] != paddingF.Md5 {
|
||||
// logrus.Debugln("remote md5 is", m["padding-md5"])
|
||||
f := newFrame(cmdUpdatePaddingScheme, 0)
|
||||
f.data = paddingF.RawScheme
|
||||
_, err = s.writeFrame(f)
|
||||
if err != nil {
|
||||
pool.Put(buffer)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
pool.Put(buffer)
|
||||
}
|
||||
case cmdAlert:
|
||||
if hdr.Length() > 0 {
|
||||
buffer := pool.Get(int(hdr.Length()))
|
||||
if _, err := io.ReadFull(s.conn, buffer); err != nil {
|
||||
pool.Put(buffer)
|
||||
return err
|
||||
}
|
||||
if s.isClient {
|
||||
log.Errorln("[Alert from server] %s", string(buffer))
|
||||
}
|
||||
pool.Put(buffer)
|
||||
return nil
|
||||
}
|
||||
case cmdUpdatePaddingScheme:
|
||||
if hdr.Length() > 0 {
|
||||
// `rawScheme` Do not use buffer to prevent subsequent misuse
|
||||
rawScheme := make([]byte, int(hdr.Length()))
|
||||
if _, err := io.ReadFull(s.conn, rawScheme); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.isClient {
|
||||
if padding.UpdatePaddingScheme(rawScheme, s.padding) {
|
||||
log.Infoln("[Update padding succeed] %x\n", md5.Sum(rawScheme))
|
||||
} else {
|
||||
log.Warnln("[Update padding failed] %x\n", md5.Sum(rawScheme))
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
// I don't know what command it is (can't have data)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notify the session that a stream has closed
|
||||
func (s *Session) streamClosed(sid uint32) error {
|
||||
_, err := s.writeFrame(newFrame(cmdFIN, sid))
|
||||
s.streamLock.Lock()
|
||||
delete(s.streams, sid)
|
||||
s.streamLock.Unlock()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Session) writeFrame(frame frame) (int, error) {
|
||||
dataLen := len(frame.data)
|
||||
|
||||
buffer := buf.NewSize(dataLen + headerOverHeadSize)
|
||||
buffer.WriteByte(frame.cmd)
|
||||
binary.BigEndian.PutUint32(buffer.Extend(4), frame.sid)
|
||||
binary.BigEndian.PutUint16(buffer.Extend(2), uint16(dataLen))
|
||||
buffer.Write(frame.data)
|
||||
_, err := s.writeConn(buffer.Bytes())
|
||||
buffer.Release()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return dataLen, nil
|
||||
}
|
||||
|
||||
func (s *Session) writeConn(b []byte) (n int, err error) {
|
||||
s.connLock.Lock()
|
||||
defer s.connLock.Unlock()
|
||||
|
||||
if s.buffering {
|
||||
s.buffer = append(s.buffer, b...)
|
||||
return len(b), nil
|
||||
} else if len(s.buffer) > 0 {
|
||||
b = append(s.buffer, b...)
|
||||
s.buffer = nil
|
||||
}
|
||||
|
||||
// calulate & send padding
|
||||
if s.sendPadding {
|
||||
pkt := s.pktCounter.Add(1)
|
||||
paddingF := s.padding.Load()
|
||||
if pkt < paddingF.Stop {
|
||||
pktSizes := paddingF.GenerateRecordPayloadSizes(pkt)
|
||||
for _, l := range pktSizes {
|
||||
remainPayloadLen := len(b)
|
||||
if l == padding.CheckMark {
|
||||
if remainPayloadLen == 0 {
|
||||
break
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if remainPayloadLen > l { // this packet is all payload
|
||||
_, err = s.conn.Write(b[:l])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n += l
|
||||
b = b[l:]
|
||||
} else if remainPayloadLen > 0 { // this packet contains padding and the last part of payload
|
||||
paddingLen := l - remainPayloadLen - headerOverHeadSize
|
||||
if paddingLen > 0 {
|
||||
padding := make([]byte, headerOverHeadSize+paddingLen)
|
||||
padding[0] = cmdWaste
|
||||
binary.BigEndian.PutUint32(padding[1:5], 0)
|
||||
binary.BigEndian.PutUint16(padding[5:7], uint16(paddingLen))
|
||||
b = append(b, padding...)
|
||||
}
|
||||
_, err = s.conn.Write(b)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n += remainPayloadLen
|
||||
b = nil
|
||||
} else { // this packet is all padding
|
||||
padding := make([]byte, headerOverHeadSize+l)
|
||||
padding[0] = cmdWaste
|
||||
binary.BigEndian.PutUint32(padding[1:5], 0)
|
||||
binary.BigEndian.PutUint16(padding[5:7], uint16(l))
|
||||
_, err = s.conn.Write(padding)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
b = nil
|
||||
}
|
||||
}
|
||||
// maybe still remain payload to write
|
||||
if len(b) == 0 {
|
||||
return
|
||||
} else {
|
||||
n2, err := s.conn.Write(b)
|
||||
return n + n2, err
|
||||
}
|
||||
} else {
|
||||
s.sendPadding = false
|
||||
}
|
||||
}
|
||||
|
||||
return s.conn.Write(b)
|
||||
}
|
||||
110
transport/anytls/session/stream.go
Normal file
110
transport/anytls/session/stream.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/transport/anytls/pipe"
|
||||
)
|
||||
|
||||
// Stream implements net.Conn
|
||||
type Stream struct {
|
||||
id uint32
|
||||
|
||||
sess *Session
|
||||
|
||||
pipeR *pipe.PipeReader
|
||||
pipeW *pipe.PipeWriter
|
||||
writeDeadline pipe.PipeDeadline
|
||||
|
||||
dieOnce sync.Once
|
||||
dieHook func()
|
||||
}
|
||||
|
||||
// newStream initiates a Stream struct
|
||||
func newStream(id uint32, sess *Session) *Stream {
|
||||
s := new(Stream)
|
||||
s.id = id
|
||||
s.sess = sess
|
||||
s.pipeR, s.pipeW = pipe.Pipe()
|
||||
s.writeDeadline = pipe.MakePipeDeadline()
|
||||
return s
|
||||
}
|
||||
|
||||
// Read implements net.Conn
|
||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||
return s.pipeR.Read(b)
|
||||
}
|
||||
|
||||
// Write implements net.Conn
|
||||
func (s *Stream) Write(b []byte) (n int, err error) {
|
||||
select {
|
||||
case <-s.writeDeadline.Wait():
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
default:
|
||||
}
|
||||
f := newFrame(cmdPSH, s.id)
|
||||
f.data = b
|
||||
n, err = s.sess.writeFrame(f)
|
||||
return
|
||||
}
|
||||
|
||||
// Close implements net.Conn
|
||||
func (s *Stream) Close() error {
|
||||
if s.sessionClose() {
|
||||
// notify remote
|
||||
return s.sess.streamClosed(s.id)
|
||||
} else {
|
||||
return io.ErrClosedPipe
|
||||
}
|
||||
}
|
||||
|
||||
// sessionClose close stream from session side, do not notify remote
|
||||
func (s *Stream) sessionClose() (once bool) {
|
||||
s.dieOnce.Do(func() {
|
||||
s.pipeR.Close()
|
||||
once = true
|
||||
if s.dieHook != nil {
|
||||
s.dieHook()
|
||||
s.dieHook = nil
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Stream) SetReadDeadline(t time.Time) error {
|
||||
return s.pipeR.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
func (s *Stream) SetWriteDeadline(t time.Time) error {
|
||||
s.writeDeadline.Set(t)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Stream) SetDeadline(t time.Time) error {
|
||||
s.SetWriteDeadline(t)
|
||||
return s.SetReadDeadline(t)
|
||||
}
|
||||
|
||||
// LocalAddr satisfies net.Conn interface
|
||||
func (s *Stream) LocalAddr() net.Addr {
|
||||
if ts, ok := s.sess.conn.(interface {
|
||||
LocalAddr() net.Addr
|
||||
}); ok {
|
||||
return ts.LocalAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteAddr satisfies net.Conn interface
|
||||
func (s *Stream) RemoteAddr() net.Addr {
|
||||
if ts, ok := s.sess.conn.(interface {
|
||||
RemoteAddr() net.Addr
|
||||
}); ok {
|
||||
return ts.RemoteAddr()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
46
transport/anytls/skiplist/contianer.go
Normal file
46
transport/anytls/skiplist/contianer.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package skiplist
|
||||
|
||||
// Container is a holder object that stores a collection of other objects.
|
||||
type Container interface {
|
||||
IsEmpty() bool // IsEmpty checks if the container has no elements.
|
||||
Len() int // Len returns the number of elements in the container.
|
||||
Clear() // Clear erases all elements from the container. After this call, Len() returns zero.
|
||||
}
|
||||
|
||||
// Map is a associative container that contains key-value pairs with unique keys.
|
||||
type Map[K any, V any] interface {
|
||||
Container
|
||||
Has(K) bool // Checks whether the container contains element with specific key.
|
||||
Find(K) *V // Finds element with specific key.
|
||||
Insert(K, V) // Inserts a key-value pair in to the container or replace existing value.
|
||||
Remove(K) bool // Remove element with specific key.
|
||||
ForEach(func(K, V)) // Iterate the container.
|
||||
ForEachIf(func(K, V) bool) // Iterate the container, stops when the callback returns false.
|
||||
ForEachMutable(func(K, *V)) // Iterate the container, *V is mutable.
|
||||
ForEachMutableIf(func(K, *V) bool) // Iterate the container, *V is mutable, stops when the callback returns false.
|
||||
}
|
||||
|
||||
// Set is a containers that store unique elements.
|
||||
type Set[K any] interface {
|
||||
Container
|
||||
Has(K) bool // Checks whether the container contains element with specific key.
|
||||
Insert(K) // Inserts a key-value pair in to the container or replace existing value.
|
||||
InsertN(...K) // Inserts multiple key-value pairs in to the container or replace existing value.
|
||||
Remove(K) bool // Remove element with specific key.
|
||||
RemoveN(...K) // Remove multiple elements with specific keys.
|
||||
ForEach(func(K)) // Iterate the container.
|
||||
ForEachIf(func(K) bool) // Iterate the container, stops when the callback returns false.
|
||||
}
|
||||
|
||||
// Iterator is the interface for container's iterator.
|
||||
type Iterator[T any] interface {
|
||||
IsNotEnd() bool // Whether it is point to the end of the range.
|
||||
MoveToNext() // Let it point to the next element.
|
||||
Value() T // Return the value of current element.
|
||||
}
|
||||
|
||||
// MapIterator is the interface for map's iterator.
|
||||
type MapIterator[K any, V any] interface {
|
||||
Iterator[V]
|
||||
Key() K // The key of the element
|
||||
}
|
||||
457
transport/anytls/skiplist/skiplist.go
Normal file
457
transport/anytls/skiplist/skiplist.go
Normal file
@@ -0,0 +1,457 @@
|
||||
package skiplist
|
||||
|
||||
// This implementation is based on https://github.com/liyue201/gostl/tree/master/ds/skiplist
|
||||
// (many thanks), added many optimizations, such as:
|
||||
//
|
||||
// - adaptive level
|
||||
// - lesser search for prevs when key already exists.
|
||||
// - reduce memory allocations
|
||||
// - richer interface.
|
||||
//
|
||||
// etc.
|
||||
|
||||
import (
|
||||
"math/bits"
|
||||
"math/rand"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
skipListMaxLevel = 40
|
||||
)
|
||||
|
||||
// SkipList is a probabilistic data structure that seem likely to supplant balanced trees as the
|
||||
// implementation method of choice for many applications. Skip list algorithms have the same
|
||||
// asymptotic expected time bounds as balanced trees and are simpler, faster and use less space.
|
||||
//
|
||||
// See https://en.wikipedia.org/wiki/Skip_list for more details.
|
||||
type SkipList[K any, V any] struct {
|
||||
level int // Current level, may increase dynamically during insertion
|
||||
len int // Total elements numner in the skiplist.
|
||||
head skipListNode[K, V] // head.next[level] is the head of each level.
|
||||
// This cache is used to save the previous nodes when modifying the skip list to avoid
|
||||
// allocating memory each time it is called.
|
||||
prevsCache []*skipListNode[K, V]
|
||||
rander *rand.Rand
|
||||
impl skipListImpl[K, V]
|
||||
}
|
||||
|
||||
// NewSkipList creates a new SkipList for Ordered key type.
|
||||
func NewSkipList[K Ordered, V any]() *SkipList[K, V] {
|
||||
sl := skipListOrdered[K, V]{}
|
||||
sl.init()
|
||||
sl.impl = (skipListImpl[K, V])(&sl)
|
||||
return &sl.SkipList
|
||||
}
|
||||
|
||||
// NewSkipListFromMap creates a new SkipList from a map.
|
||||
func NewSkipListFromMap[K Ordered, V any](m map[K]V) *SkipList[K, V] {
|
||||
sl := NewSkipList[K, V]()
|
||||
for k, v := range m {
|
||||
sl.Insert(k, v)
|
||||
}
|
||||
return sl
|
||||
}
|
||||
|
||||
// NewSkipListFunc creates a new SkipList with specified compare function keyCmp.
|
||||
func NewSkipListFunc[K any, V any](keyCmp CompareFn[K]) *SkipList[K, V] {
|
||||
sl := skipListFunc[K, V]{}
|
||||
sl.init()
|
||||
sl.keyCmp = keyCmp
|
||||
sl.impl = skipListImpl[K, V](&sl)
|
||||
return &sl.SkipList
|
||||
}
|
||||
|
||||
// IsEmpty implements the Container interface.
|
||||
func (sl *SkipList[K, V]) IsEmpty() bool {
|
||||
return sl.len == 0
|
||||
}
|
||||
|
||||
// Len implements the Container interface.
|
||||
func (sl *SkipList[K, V]) Len() int {
|
||||
return sl.len
|
||||
}
|
||||
|
||||
// Clear implements the Container interface.
|
||||
func (sl *SkipList[K, V]) Clear() {
|
||||
for i := range sl.head.next {
|
||||
sl.head.next[i] = nil
|
||||
}
|
||||
sl.level = 1
|
||||
sl.len = 0
|
||||
}
|
||||
|
||||
// Iterate return an iterator to the skiplist.
|
||||
func (sl *SkipList[K, V]) Iterate() MapIterator[K, V] {
|
||||
return &skipListIterator[K, V]{sl.head.next[0], nil}
|
||||
}
|
||||
|
||||
// Insert inserts a key-value pair into the skiplist.
|
||||
// If the key is already in the skip list, it's value will be updated.
|
||||
func (sl *SkipList[K, V]) Insert(key K, value V) {
|
||||
node, prevs := sl.impl.findInsertPoint(key)
|
||||
|
||||
if node != nil {
|
||||
// Already exist, update the value
|
||||
node.value = value
|
||||
return
|
||||
}
|
||||
|
||||
level := sl.randomLevel()
|
||||
node = newSkipListNode(level, key, value)
|
||||
|
||||
minLevel := level
|
||||
if sl.level < level {
|
||||
minLevel = sl.level
|
||||
}
|
||||
for i := 0; i < minLevel; i++ {
|
||||
node.next[i] = prevs[i].next[i]
|
||||
prevs[i].next[i] = node
|
||||
}
|
||||
|
||||
if level > sl.level {
|
||||
for i := sl.level; i < level; i++ {
|
||||
sl.head.next[i] = node
|
||||
}
|
||||
sl.level = level
|
||||
}
|
||||
|
||||
sl.len++
|
||||
}
|
||||
|
||||
// Find returns the value associated with the passed key if the key is in the skiplist, otherwise
|
||||
// returns nil.
|
||||
func (sl *SkipList[K, V]) Find(key K) *V {
|
||||
node := sl.impl.findNode(key)
|
||||
if node != nil {
|
||||
return &node.value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Has implement the Map interface.
|
||||
func (sl *SkipList[K, V]) Has(key K) bool {
|
||||
return sl.impl.findNode(key) != nil
|
||||
}
|
||||
|
||||
// LowerBound returns an iterator to the first element in the skiplist that
|
||||
// does not satisfy element < value (i.e. greater or equal to),
|
||||
// or a end itetator if no such element is found.
|
||||
func (sl *SkipList[K, V]) LowerBound(key K) MapIterator[K, V] {
|
||||
return &skipListIterator[K, V]{sl.impl.lowerBound(key), nil}
|
||||
}
|
||||
|
||||
// UpperBound returns an iterator to the first element in the skiplist that
|
||||
// does not satisfy value < element (i.e. strictly greater),
|
||||
// or a end itetator if no such element is found.
|
||||
func (sl *SkipList[K, V]) UpperBound(key K) MapIterator[K, V] {
|
||||
return &skipListIterator[K, V]{sl.impl.upperBound(key), nil}
|
||||
}
|
||||
|
||||
// FindRange returns an iterator in range [first, last) (last is not includeed).
|
||||
func (sl *SkipList[K, V]) FindRange(first, last K) MapIterator[K, V] {
|
||||
return &skipListIterator[K, V]{sl.impl.lowerBound(first), sl.impl.upperBound(last)}
|
||||
}
|
||||
|
||||
// Remove removes the key-value pair associated with the passed key and returns true if the key is
|
||||
// in the skiplist, otherwise returns false.
|
||||
func (sl *SkipList[K, V]) Remove(key K) bool {
|
||||
node, prevs := sl.impl.findRemovePoint(key)
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
for i, v := range node.next {
|
||||
prevs[i].next[i] = v
|
||||
}
|
||||
for sl.level > 1 && sl.head.next[sl.level-1] == nil {
|
||||
sl.level--
|
||||
}
|
||||
sl.len--
|
||||
return true
|
||||
}
|
||||
|
||||
// ForEach implements the Map interface.
|
||||
func (sl *SkipList[K, V]) ForEach(op func(K, V)) {
|
||||
for e := sl.head.next[0]; e != nil; e = e.next[0] {
|
||||
op(e.key, e.value)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachMutable implements the Map interface.
|
||||
func (sl *SkipList[K, V]) ForEachMutable(op func(K, *V)) {
|
||||
for e := sl.head.next[0]; e != nil; e = e.next[0] {
|
||||
op(e.key, &e.value)
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachIf implements the Map interface.
|
||||
func (sl *SkipList[K, V]) ForEachIf(op func(K, V) bool) {
|
||||
for e := sl.head.next[0]; e != nil; e = e.next[0] {
|
||||
if !op(e.key, e.value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ForEachMutableIf implements the Map interface.
|
||||
func (sl *SkipList[K, V]) ForEachMutableIf(op func(K, *V) bool) {
|
||||
for e := sl.head.next[0]; e != nil; e = e.next[0] {
|
||||
if !op(e.key, &e.value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// SkipList implementation part.
|
||||
|
||||
type skipListNode[K any, V any] struct {
|
||||
key K
|
||||
value V
|
||||
next []*skipListNode[K, V]
|
||||
}
|
||||
|
||||
//go:generate bash ./skiplist_newnode_generate.sh skipListMaxLevel skiplist_newnode.go
|
||||
// func newSkipListNode[K Ordered, V any](level int, key K, value V) *skipListNode[K, V]
|
||||
|
||||
type skipListIterator[K any, V any] struct {
|
||||
node, end *skipListNode[K, V]
|
||||
}
|
||||
|
||||
func (it *skipListIterator[K, V]) IsNotEnd() bool {
|
||||
return it.node != it.end
|
||||
}
|
||||
|
||||
func (it *skipListIterator[K, V]) MoveToNext() {
|
||||
it.node = it.node.next[0]
|
||||
}
|
||||
|
||||
func (it *skipListIterator[K, V]) Key() K {
|
||||
return it.node.key
|
||||
}
|
||||
|
||||
func (it *skipListIterator[K, V]) Value() V {
|
||||
return it.node.value
|
||||
}
|
||||
|
||||
// skipListImpl is an interface to provide different implementation for Ordered key or CompareFn.
|
||||
//
|
||||
// We can use CompareFn to cumpare Ordered keys, but a separated implementation is much faster.
|
||||
// We don't make the whole skip list an interface, in order to share the type independented method.
|
||||
// And because these methods are called directly without going through the interface, they are also
|
||||
// much faster.
|
||||
type skipListImpl[K any, V any] interface {
|
||||
findNode(key K) *skipListNode[K, V]
|
||||
lowerBound(key K) *skipListNode[K, V]
|
||||
upperBound(key K) *skipListNode[K, V]
|
||||
findInsertPoint(key K) (*skipListNode[K, V], []*skipListNode[K, V])
|
||||
findRemovePoint(key K) (*skipListNode[K, V], []*skipListNode[K, V])
|
||||
}
|
||||
|
||||
func (sl *SkipList[K, V]) init() {
|
||||
sl.level = 1
|
||||
// #nosec G404 -- This is not a security condition
|
||||
sl.rander = rand.New(rand.NewSource(time.Now().Unix()))
|
||||
sl.prevsCache = make([]*skipListNode[K, V], skipListMaxLevel)
|
||||
sl.head.next = make([]*skipListNode[K, V], skipListMaxLevel)
|
||||
}
|
||||
|
||||
func (sl *SkipList[K, V]) randomLevel() int {
|
||||
total := uint64(1)<<uint64(skipListMaxLevel) - 1 // 2^n-1
|
||||
k := sl.rander.Uint64() % total
|
||||
level := skipListMaxLevel - bits.Len64(k) + 1
|
||||
// Since levels are randomly generated, most should be less than log2(s.len).
|
||||
// Then make a limit according to sl.len to avoid unexpectedly large value.
|
||||
for level > 3 && 1<<(level-3) > sl.len {
|
||||
level--
|
||||
}
|
||||
|
||||
return level
|
||||
}
|
||||
|
||||
/// skipListOrdered part
|
||||
|
||||
// skipListOrdered is the skip list implementation for Ordered types.
|
||||
type skipListOrdered[K Ordered, V any] struct {
|
||||
SkipList[K, V]
|
||||
}
|
||||
|
||||
func (sl *skipListOrdered[K, V]) findNode(key K) *skipListNode[K, V] {
|
||||
return sl.doFindNode(key, true)
|
||||
}
|
||||
|
||||
func (sl *skipListOrdered[K, V]) doFindNode(key K, eq bool) *skipListNode[K, V] {
|
||||
// This function execute the job of findNode if eq is true, otherwise lowBound.
|
||||
// Passing the control variable eq is ugly but it's faster than testing node
|
||||
// again outside the function in findNode.
|
||||
prev := &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for cur := prev.next[i]; cur != nil; cur = cur.next[i] {
|
||||
if cur.key == key {
|
||||
return cur
|
||||
}
|
||||
if cur.key > key {
|
||||
// All other node in this level must be greater than the key,
|
||||
// search the next level.
|
||||
break
|
||||
}
|
||||
prev = cur
|
||||
}
|
||||
}
|
||||
if eq {
|
||||
return nil
|
||||
}
|
||||
return prev.next[0]
|
||||
}
|
||||
|
||||
func (sl *skipListOrdered[K, V]) lowerBound(key K) *skipListNode[K, V] {
|
||||
return sl.doFindNode(key, false)
|
||||
}
|
||||
|
||||
func (sl *skipListOrdered[K, V]) upperBound(key K) *skipListNode[K, V] {
|
||||
node := sl.lowerBound(key)
|
||||
if node != nil && node.key == key {
|
||||
return node.next[0]
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// findInsertPoint returns (*node, nil) to the existed node if the key exists,
|
||||
// or (nil, []*node) to the previous nodes if the key doesn't exist
|
||||
func (sl *skipListOrdered[K, V]) findInsertPoint(key K) (*skipListNode[K, V], []*skipListNode[K, V]) {
|
||||
prevs := sl.prevsCache[0:sl.level]
|
||||
prev := &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for next := prev.next[i]; next != nil; next = next.next[i] {
|
||||
if next.key == key {
|
||||
// The key is already existed, prevs are useless because no new node insertion.
|
||||
// stop searching.
|
||||
return next, nil
|
||||
}
|
||||
if next.key > key {
|
||||
// All other node in this level must be greater than the key,
|
||||
// search the next level.
|
||||
break
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
prevs[i] = prev
|
||||
}
|
||||
return nil, prevs
|
||||
}
|
||||
|
||||
// findRemovePoint finds the node which match the key and it's previous nodes.
|
||||
func (sl *skipListOrdered[K, V]) findRemovePoint(key K) (*skipListNode[K, V], []*skipListNode[K, V]) {
|
||||
prevs := sl.findPrevNodes(key)
|
||||
node := prevs[0].next[0]
|
||||
if node == nil || node.key != key {
|
||||
return nil, nil
|
||||
}
|
||||
return node, prevs
|
||||
}
|
||||
|
||||
func (sl *skipListOrdered[K, V]) findPrevNodes(key K) []*skipListNode[K, V] {
|
||||
prevs := sl.prevsCache[0:sl.level]
|
||||
prev := &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for next := prev.next[i]; next != nil; next = next.next[i] {
|
||||
if next.key >= key {
|
||||
break
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
prevs[i] = prev
|
||||
}
|
||||
return prevs
|
||||
}
|
||||
|
||||
/// skipListFunc part
|
||||
|
||||
// skipListFunc is the skip list implementation which compare keys with func.
|
||||
type skipListFunc[K any, V any] struct {
|
||||
SkipList[K, V]
|
||||
keyCmp CompareFn[K]
|
||||
}
|
||||
|
||||
func (sl *skipListFunc[K, V]) findNode(key K) *skipListNode[K, V] {
|
||||
node := sl.lowerBound(key)
|
||||
if node != nil && sl.keyCmp(node.key, key) == 0 {
|
||||
return node
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sl *skipListFunc[K, V]) lowerBound(key K) *skipListNode[K, V] {
|
||||
var prev = &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
cur := prev.next[i]
|
||||
for ; cur != nil; cur = cur.next[i] {
|
||||
cmpRet := sl.keyCmp(cur.key, key)
|
||||
if cmpRet == 0 {
|
||||
return cur
|
||||
}
|
||||
if cmpRet > 0 {
|
||||
break
|
||||
}
|
||||
prev = cur
|
||||
}
|
||||
}
|
||||
return prev.next[0]
|
||||
}
|
||||
|
||||
func (sl *skipListFunc[K, V]) upperBound(key K) *skipListNode[K, V] {
|
||||
node := sl.lowerBound(key)
|
||||
if node != nil && sl.keyCmp(node.key, key) == 0 {
|
||||
return node.next[0]
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// findInsertPoint returns (*node, nil) to the existed node if the key exists,
|
||||
// or (nil, []*node) to the previous nodes if the key doesn't exist
|
||||
func (sl *skipListFunc[K, V]) findInsertPoint(key K) (*skipListNode[K, V], []*skipListNode[K, V]) {
|
||||
prevs := sl.prevsCache[0:sl.level]
|
||||
prev := &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for cur := prev.next[i]; cur != nil; cur = cur.next[i] {
|
||||
r := sl.keyCmp(cur.key, key)
|
||||
if r == 0 {
|
||||
// The key is already existed, prevs are useless because no new node insertion.
|
||||
// stop searching.
|
||||
return cur, nil
|
||||
}
|
||||
if r > 0 {
|
||||
// All other node in this level must be greater than the key,
|
||||
// search the next level.
|
||||
break
|
||||
}
|
||||
prev = cur
|
||||
}
|
||||
prevs[i] = prev
|
||||
}
|
||||
return nil, prevs
|
||||
}
|
||||
|
||||
// findRemovePoint finds the node which match the key and it's previous nodes.
|
||||
func (sl *skipListFunc[K, V]) findRemovePoint(key K) (*skipListNode[K, V], []*skipListNode[K, V]) {
|
||||
prevs := sl.findPrevNodes(key)
|
||||
node := prevs[0].next[0]
|
||||
if node == nil || sl.keyCmp(node.key, key) != 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return node, prevs
|
||||
}
|
||||
|
||||
func (sl *skipListFunc[K, V]) findPrevNodes(key K) []*skipListNode[K, V] {
|
||||
prevs := sl.prevsCache[0:sl.level]
|
||||
prev := &sl.head
|
||||
for i := sl.level - 1; i >= 0; i-- {
|
||||
for next := prev.next[i]; next != nil; next = next.next[i] {
|
||||
if sl.keyCmp(next.key, key) >= 0 {
|
||||
break
|
||||
}
|
||||
prev = next
|
||||
}
|
||||
prevs[i] = prev
|
||||
}
|
||||
return prevs
|
||||
}
|
||||
297
transport/anytls/skiplist/skiplist_newnode.go
Normal file
297
transport/anytls/skiplist/skiplist_newnode.go
Normal file
@@ -0,0 +1,297 @@
|
||||
// AUTO GENERATED CODE, DON'T EDIT!!!
|
||||
// EDIT skiplist_newnode_generate.sh accordingly.
|
||||
|
||||
package skiplist
|
||||
|
||||
// newSkipListNode creates a new node initialized with specified key, value and next slice.
|
||||
func newSkipListNode[K any, V any](level int, key K, value V) *skipListNode[K, V] {
|
||||
// For nodes with each levels, point their next slice to the nexts array allocated together,
|
||||
// which can reduce 1 memory allocation and improve performance.
|
||||
//
|
||||
// The generics of the golang doesn't support non-type parameters like in C++,
|
||||
// so we have to generate it manually.
|
||||
switch level {
|
||||
case 1:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [1]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 2:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [2]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 3:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [3]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 4:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [4]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 5:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [5]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 6:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [6]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 7:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [7]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 8:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [8]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 9:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [9]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 10:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [10]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 11:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [11]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 12:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [12]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 13:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [13]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 14:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [14]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 15:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [15]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 16:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [16]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 17:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [17]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 18:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [18]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 19:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [19]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 20:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [20]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 21:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [21]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 22:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [22]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 23:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [23]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 24:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [24]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 25:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [25]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 26:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [26]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 27:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [27]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 28:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [28]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 29:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [29]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 30:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [30]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 31:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [31]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 32:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [32]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 33:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [33]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 34:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [34]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 35:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [35]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 36:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [36]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 37:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [37]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 38:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [38]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 39:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [39]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
case 40:
|
||||
n := struct {
|
||||
head skipListNode[K, V]
|
||||
nexts [40]*skipListNode[K, V]
|
||||
}{head: skipListNode[K, V]{key, value, nil}}
|
||||
n.head.next = n.nexts[:]
|
||||
return &n.head
|
||||
}
|
||||
|
||||
panic("should not reach here")
|
||||
}
|
||||
75
transport/anytls/skiplist/types.go
Normal file
75
transport/anytls/skiplist/types.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package skiplist
|
||||
|
||||
// Signed is a constraint that permits any signed integer type.
|
||||
// If future releases of Go add new predeclared signed integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Signed interface {
|
||||
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||
}
|
||||
|
||||
// Unsigned is a constraint that permits any unsigned integer type.
|
||||
// If future releases of Go add new predeclared unsigned integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Unsigned interface {
|
||||
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||
}
|
||||
|
||||
// Integer is a constraint that permits any integer type.
|
||||
// If future releases of Go add new predeclared integer types,
|
||||
// this constraint will be modified to include them.
|
||||
type Integer interface {
|
||||
Signed | Unsigned
|
||||
}
|
||||
|
||||
// Float is a constraint that permits any floating-point type.
|
||||
// If future releases of Go add new predeclared floating-point types,
|
||||
// this constraint will be modified to include them.
|
||||
type Float interface {
|
||||
~float32 | ~float64
|
||||
}
|
||||
|
||||
// Ordered is a constraint that permits any ordered type: any type
|
||||
// that supports the operators < <= >= >.
|
||||
// If future releases of Go add new ordered types,
|
||||
// this constraint will be modified to include them.
|
||||
type Ordered interface {
|
||||
Integer | Float | ~string
|
||||
}
|
||||
|
||||
// Numeric is a constraint that permits any numeric type.
|
||||
type Numeric interface {
|
||||
Integer | Float
|
||||
}
|
||||
|
||||
// LessFn is a function that returns whether 'a' is less than 'b'.
|
||||
type LessFn[T any] func(a, b T) bool
|
||||
|
||||
// CompareFn is a 3 way compare function that
|
||||
// returns 1 if a > b,
|
||||
// returns 0 if a == b,
|
||||
// returns -1 if a < b.
|
||||
type CompareFn[T any] func(a, b T) int
|
||||
|
||||
// HashFn is a function that returns the hash of 't'.
|
||||
type HashFn[T any] func(t T) uint64
|
||||
|
||||
// Equals wraps the '==' operator for comparable types.
|
||||
func Equals[T comparable](a, b T) bool {
|
||||
return a == b
|
||||
}
|
||||
|
||||
// Less wraps the '<' operator for ordered types.
|
||||
func Less[T Ordered](a, b T) bool {
|
||||
return a < b
|
||||
}
|
||||
|
||||
// OrderedCompare provide default CompareFn for ordered types.
|
||||
func OrderedCompare[T Ordered](a, b T) int {
|
||||
if a < b {
|
||||
return -1
|
||||
}
|
||||
if a > b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
28
transport/anytls/util/routine.go
Normal file
28
transport/anytls/util/routine.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
func StartRoutine(ctx context.Context, d time.Duration, f func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Errorln("[BUG] %v %s", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
for {
|
||||
time.Sleep(d)
|
||||
f()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
27
transport/anytls/util/string_map.go
Normal file
27
transport/anytls/util/string_map.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type StringMap map[string]string
|
||||
|
||||
func (s StringMap) ToBytes() []byte {
|
||||
var lines []string
|
||||
for k, v := range s {
|
||||
lines = append(lines, k+"="+v)
|
||||
}
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func StringMapFromBytes(b []byte) StringMap {
|
||||
var m = make(StringMap)
|
||||
var lines = strings.Split(string(b), "\n")
|
||||
for _, line := range lines {
|
||||
v := strings.SplitN(line, "=", 2)
|
||||
if len(v) == 2 {
|
||||
m[v[0]] = v[1]
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
8
transport/anytls/util/type.go
Normal file
8
transport/anytls/util/type.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type DialOutFunc func(ctx context.Context) (net.Conn, error)
|
||||
@@ -38,15 +38,17 @@ var defaultHeader = http.Header{
|
||||
type DialFn = func(network, addr string) (net.Conn, error)
|
||||
|
||||
type Conn struct {
|
||||
response *http.Response
|
||||
request *http.Request
|
||||
transport *TransportWrap
|
||||
writer *io.PipeWriter
|
||||
once sync.Once
|
||||
close atomic.Bool
|
||||
err error
|
||||
remain int
|
||||
br *bufio.Reader
|
||||
initFn func() (io.ReadCloser, error)
|
||||
writer io.Writer
|
||||
flusher http.Flusher
|
||||
netAddr
|
||||
|
||||
reader io.ReadCloser
|
||||
once sync.Once
|
||||
close atomic.Bool
|
||||
err error
|
||||
remain int
|
||||
br *bufio.Reader
|
||||
// deadlines
|
||||
deadline *time.Timer
|
||||
}
|
||||
@@ -57,26 +59,32 @@ type Config struct {
|
||||
ClientFingerprint string
|
||||
}
|
||||
|
||||
func (g *Conn) initRequest() {
|
||||
response, err := g.transport.RoundTrip(g.request)
|
||||
func (g *Conn) initReader() {
|
||||
reader, err := g.initFn()
|
||||
if err != nil {
|
||||
g.err = err
|
||||
g.writer.Close()
|
||||
if closer, ok := g.writer.(io.Closer); ok {
|
||||
closer.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if !g.close.Load() {
|
||||
g.response = response
|
||||
g.br = bufio.NewReader(response.Body)
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
} else {
|
||||
response.Body.Close()
|
||||
reader.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Conn) Init() error {
|
||||
g.once.Do(g.initReader)
|
||||
return g.err
|
||||
}
|
||||
|
||||
func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
g.once.Do(g.initRequest)
|
||||
if g.err != nil {
|
||||
return 0, g.err
|
||||
if err = g.Init(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if g.remain > 0 {
|
||||
@@ -88,7 +96,7 @@ func (g *Conn) Read(b []byte) (n int, err error) {
|
||||
n, err = io.ReadFull(g.br, b[:size])
|
||||
g.remain -= n
|
||||
return
|
||||
} else if g.response == nil {
|
||||
} else if g.reader == nil {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
|
||||
@@ -139,6 +147,10 @@ func (g *Conn) Write(b []byte) (n int, err error) {
|
||||
err = g.err
|
||||
}
|
||||
|
||||
if g.flusher != nil {
|
||||
g.flusher.Flush()
|
||||
}
|
||||
|
||||
return len(b), err
|
||||
}
|
||||
|
||||
@@ -158,6 +170,10 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||
err = g.err
|
||||
}
|
||||
|
||||
if g.flusher != nil {
|
||||
g.flusher.Flush()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -167,15 +183,16 @@ func (g *Conn) FrontHeadroom() int {
|
||||
|
||||
func (g *Conn) Close() error {
|
||||
g.close.Store(true)
|
||||
if r := g.response; r != nil {
|
||||
r.Body.Close()
|
||||
if reader := g.reader; reader != nil {
|
||||
reader.Close()
|
||||
}
|
||||
|
||||
return g.writer.Close()
|
||||
if closer, ok := g.writer.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Conn) LocalAddr() net.Addr { return g.transport.LocalAddr() }
|
||||
func (g *Conn) RemoteAddr() net.Addr { return g.transport.RemoteAddr() }
|
||||
func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
|
||||
func (g *Conn) SetWriteDeadline(t time.Time) error { return g.SetDeadline(t) }
|
||||
|
||||
@@ -200,6 +217,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
||||
return nil, err
|
||||
}
|
||||
wrap.remoteAddr = pconn.RemoteAddr()
|
||||
wrap.localAddr = pconn.LocalAddr()
|
||||
|
||||
if tlsConfig == nil {
|
||||
return pconn, nil
|
||||
@@ -286,13 +304,18 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
||||
}
|
||||
|
||||
conn := &Conn{
|
||||
request: request,
|
||||
transport: transport,
|
||||
writer: writer,
|
||||
close: atomic.NewBool(false),
|
||||
initFn: func() (io.ReadCloser, error) {
|
||||
response, err := transport.RoundTrip(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Body, nil
|
||||
},
|
||||
writer: writer,
|
||||
netAddr: transport.netAddr,
|
||||
}
|
||||
|
||||
go conn.once.Do(conn.initRequest)
|
||||
go conn.Init()
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
|
||||
117
transport/gun/server.go
Normal file
117
transport/gun/server.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package gun
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
const idleTimeout = 30 * time.Second
|
||||
|
||||
type ServerOption struct {
|
||||
ServiceName string
|
||||
ConnHandler func(conn net.Conn)
|
||||
HttpHandler http.Handler
|
||||
}
|
||||
|
||||
func NewServerHandler(options ServerOption) http.Handler {
|
||||
path := "/" + options.ServiceName + "/Tun"
|
||||
connHandler := options.ConnHandler
|
||||
httpHandler := options.HttpHandler
|
||||
if httpHandler == nil {
|
||||
httpHandler = http.NewServeMux()
|
||||
}
|
||||
// using h2c.NewHandler to ensure we can work in plain http2
|
||||
// and some tls conn is not *tls.Conn (like *reality.Conn)
|
||||
return h2c.NewHandler(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if request.URL.Path == path &&
|
||||
request.Method == http.MethodPost &&
|
||||
strings.HasPrefix(request.Header.Get("Content-Type"), "application/grpc") {
|
||||
|
||||
writer.Header().Set("Content-Type", "application/grpc")
|
||||
writer.Header().Set("TE", "trailers")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
|
||||
conn := &Conn{
|
||||
initFn: func() (io.ReadCloser, error) {
|
||||
return request.Body, nil
|
||||
},
|
||||
writer: writer,
|
||||
flusher: writer.(http.Flusher),
|
||||
}
|
||||
if request.RemoteAddr != "" {
|
||||
metadata := C.Metadata{}
|
||||
if err := metadata.SetRemoteAddress(request.RemoteAddr); err == nil {
|
||||
conn.remoteAddr = net.TCPAddrFromAddrPort(metadata.AddrPort())
|
||||
}
|
||||
}
|
||||
if addr, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
|
||||
conn.localAddr = addr
|
||||
}
|
||||
|
||||
wrapper := &h2ConnWrapper{
|
||||
// gun.Conn can't correct handle ReadDeadline
|
||||
// so call N.NewDeadlineConn to add a safe wrapper
|
||||
ExtendedConn: N.NewDeadlineConn(conn),
|
||||
}
|
||||
connHandler(wrapper)
|
||||
wrapper.CloseWrapper()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
httpHandler.ServeHTTP(writer, request)
|
||||
}), &http2.Server{
|
||||
IdleTimeout: idleTimeout,
|
||||
})
|
||||
}
|
||||
|
||||
// h2ConnWrapper used to avoid "panic: Write called after Handler finished" for gun.Conn
|
||||
type h2ConnWrapper struct {
|
||||
N.ExtendedConn
|
||||
access sync.Mutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) Write(p []byte) (n int, err error) {
|
||||
w.access.Lock()
|
||||
defer w.access.Unlock()
|
||||
if w.closed {
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
return w.ExtendedConn.Write(p)
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) WriteBuffer(buffer *buf.Buffer) error {
|
||||
w.access.Lock()
|
||||
defer w.access.Unlock()
|
||||
if w.closed {
|
||||
return net.ErrClosed
|
||||
}
|
||||
return w.ExtendedConn.WriteBuffer(buffer)
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) CloseWrapper() {
|
||||
w.access.Lock()
|
||||
defer w.access.Unlock()
|
||||
w.closed = true
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) Close() error {
|
||||
w.CloseWrapper()
|
||||
return w.ExtendedConn.Close()
|
||||
}
|
||||
|
||||
func (w *h2ConnWrapper) Upstream() any {
|
||||
return w.ExtendedConn
|
||||
}
|
||||
@@ -7,8 +7,7 @@ import (
|
||||
|
||||
type TransportWrap struct {
|
||||
*http2.Transport
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
netAddr
|
||||
}
|
||||
|
||||
func (tw *TransportWrap) RemoteAddr() net.Addr {
|
||||
@@ -18,3 +17,16 @@ func (tw *TransportWrap) RemoteAddr() net.Addr {
|
||||
func (tw *TransportWrap) LocalAddr() net.Addr {
|
||||
return tw.localAddr
|
||||
}
|
||||
|
||||
type netAddr struct {
|
||||
remoteAddr net.Addr
|
||||
localAddr net.Addr
|
||||
}
|
||||
|
||||
func (addr *netAddr) RemoteAddr() net.Addr {
|
||||
return addr.remoteAddr
|
||||
}
|
||||
|
||||
func (addr *netAddr) LocalAddr() net.Addr {
|
||||
return addr.localAddr
|
||||
}
|
||||
|
||||
@@ -38,10 +38,9 @@ type Command = byte
|
||||
const (
|
||||
CommandTCP byte = 1
|
||||
CommandUDP byte = 3
|
||||
CommandMux byte = 0x7f
|
||||
|
||||
// deprecated XTLS commands, as souvenirs
|
||||
commandXRD byte = 0xf0 // XTLS direct mode
|
||||
commandXRO byte = 0xf1 // XTLS origin mode
|
||||
KeyLength = 56
|
||||
)
|
||||
|
||||
type Option struct {
|
||||
@@ -65,7 +64,7 @@ type WebsocketOption struct {
|
||||
|
||||
type Trojan struct {
|
||||
option *Option
|
||||
hexPassword []byte
|
||||
hexPassword [KeyLength]byte
|
||||
}
|
||||
|
||||
func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error) {
|
||||
@@ -93,7 +92,7 @@ func (t *Trojan) StreamConn(ctx context.Context, conn net.Conn) (net.Conn, error
|
||||
ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
|
||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||
err := utlsConn.HandshakeContext(ctx)
|
||||
return utlsConn, err
|
||||
}
|
||||
} else {
|
||||
@@ -152,7 +151,7 @@ func (t *Trojan) WriteHeader(w io.Writer, command Command, socks5Addr []byte) er
|
||||
buf := pool.GetBuffer()
|
||||
defer pool.PutBuffer(buf)
|
||||
|
||||
buf.Write(t.hexPassword)
|
||||
buf.Write(t.hexPassword[:])
|
||||
buf.Write(crlf)
|
||||
|
||||
buf.WriteByte(command)
|
||||
@@ -245,7 +244,7 @@ func ReadPacket(r io.Reader, payload []byte) (net.Addr, int, int, error) {
|
||||
}
|
||||
|
||||
func New(option *Option) *Trojan {
|
||||
return &Trojan{option, hexSha224([]byte(option.Password))}
|
||||
return &Trojan{option, Key(option.Password)}
|
||||
}
|
||||
|
||||
var _ N.EnhancePacketConn = (*PacketConn)(nil)
|
||||
@@ -340,9 +339,12 @@ func (pc *PacketConn) WaitReadFrom() (data []byte, put func(), addr net.Addr, er
|
||||
return
|
||||
}
|
||||
|
||||
func hexSha224(data []byte) []byte {
|
||||
buf := make([]byte, 56)
|
||||
hash := sha256.Sum224(data)
|
||||
hex.Encode(buf, hash[:])
|
||||
return buf
|
||||
func NewPacketConn(conn net.Conn) *PacketConn {
|
||||
return &PacketConn{Conn: conn}
|
||||
}
|
||||
|
||||
func Key(password string) (key [56]byte) {
|
||||
hash := sha256.Sum224([]byte(password))
|
||||
hex.Encode(key[:], hash[:])
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
|
||||
if cfg.Reality == nil {
|
||||
utlsConn, valid := GetUTLSConn(conn, cfg.ClientFingerprint, tlsConfig)
|
||||
if valid {
|
||||
err := utlsConn.(*tlsC.UConn).HandshakeContext(ctx)
|
||||
err = utlsConn.HandshakeContext(ctx)
|
||||
return utlsConn, err
|
||||
}
|
||||
} else {
|
||||
@@ -53,7 +53,7 @@ func StreamTLSConn(ctx context.Context, conn net.Conn, cfg *TLSConfig) (net.Conn
|
||||
return tlsConn, err
|
||||
}
|
||||
|
||||
func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (net.Conn, bool) {
|
||||
func GetUTLSConn(conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config) (*tlsC.UConn, bool) {
|
||||
|
||||
if fingerprint, exists := tlsC.GetFingerprint(ClientFingerprint); exists {
|
||||
utlsConn := tlsC.UClient(conn, tlsConfig, fingerprint)
|
||||
|
||||
Reference in New Issue
Block a user