mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-20 01:00:56 +00:00
Compare commits
64 commits
Author | SHA1 | Date | |
---|---|---|---|
|
d5243adf89 | ||
|
6236cb1cf0 | ||
|
619c9dc0c6 | ||
|
9c5067e519 | ||
|
feee9b320c | ||
|
63e66f49ca | ||
|
bad61f918f | ||
|
69ce4d0f8c | ||
|
b59f11f7ac | ||
|
30d90d49f0 | ||
|
76052b5b26 | ||
|
7d7f5c8980 | ||
|
e79465d306 | ||
|
345d3d7052 | ||
|
3d806b5e4c | ||
|
b5fcd1d1d1 | ||
|
b21b8ee046 | ||
|
d0d0c392d7 | ||
|
a75e570cca | ||
|
9e0889c02c | ||
|
55cbbf7f41 | ||
|
664b134015 | ||
|
ba3c44a169 | ||
|
dcb20e2824 | ||
|
3d2cb992fa | ||
|
984535f006 | ||
|
8fa4e8122c | ||
|
7551c8a545 | ||
|
237e2edea4 | ||
|
fe01033efe | ||
|
84cd0ef688 | ||
|
cedb36df5f | ||
|
7a260f7bcf | ||
|
8085c68b6d | ||
|
dbb5b7db1c | ||
|
bfd06ebad0 | ||
|
e8af058694 | ||
|
487d7fa81f | ||
|
4b15568a29 | ||
|
cac2bf72e1 | ||
|
b2d2890866 | ||
|
8752f80595 | ||
|
a6c0c02e0d | ||
|
2acb0b71ee | ||
|
2a40eba0ca | ||
|
a22efd5c91 | ||
|
9e8f4ada47 | ||
|
09c7ee0d12 | ||
|
2a08c44f51 | ||
|
190047c8c0 | ||
|
24a9ff6d03 | ||
|
efa224373f | ||
|
b0bd4f4caf | ||
|
eaaccffcef | ||
|
e81f3a97af | ||
|
323973f22f | ||
|
ed7533ca1a | ||
|
7de24e26b4 | ||
|
622d99d000 | ||
|
7f1225b0c4 | ||
|
23ffe451f4 | ||
|
7b37fcfc8d | ||
|
daa592c7f3 | ||
|
577f64a601 |
112 changed files with 2876 additions and 1175 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -14,7 +14,7 @@ on:
|
||||||
- Alpha
|
- Alpha
|
||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
pull_request_target:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- Alpha
|
- Alpha
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|
115
.github/workflows/test.yml
vendored
Normal file
115
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
name: Test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
- "README.md"
|
||||||
|
- ".github/ISSUE_TEMPLATE/**"
|
||||||
|
branches:
|
||||||
|
- Alpha
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- Alpha
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest' # amd64 linux
|
||||||
|
- 'windows-latest' # amd64 windows
|
||||||
|
- 'macos-latest' # arm64 macos
|
||||||
|
- 'ubuntu-24.04-arm' # arm64 linux
|
||||||
|
- 'macos-13' # amd64 macos
|
||||||
|
go-version:
|
||||||
|
- '1.24'
|
||||||
|
- '1.23'
|
||||||
|
- '1.22'
|
||||||
|
- '1.21'
|
||||||
|
- '1.20'
|
||||||
|
fail-fast: false
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
GOTOOLCHAIN: local
|
||||||
|
# Fix mingw trying to be smart and converting paths https://github.com/moby/moby/issues/24029#issuecomment-250412919
|
||||||
|
MSYS_NO_PATHCONV: true
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
# 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: ${{ runner.os == 'Windows' && matrix.go-version == '1.24' }}
|
||||||
|
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
|
||||||
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.23/
|
||||||
|
# revert:
|
||||||
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
- name: Revert Golang1.23 commit for Windows7/8
|
||||||
|
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.23' }}
|
||||||
|
run: |
|
||||||
|
cd $(go env GOROOT)
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/9ac42137ef6730e8b7daca016ece831297a1d75b.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/21290de8a4c91408de7c2b5b68757b1e90af49dd.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/6a31d3fa8e47ddabc10bd97bff10d9a85f4cfb76.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/69e2eed6dd0f6d815ebf15797761c13f31213dd6.diff | patch --verbose -p 1
|
||||||
|
|
||||||
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
|
# this patch file only works on golang1.22.x
|
||||||
|
# that means after golang1.23 release it must be changed
|
||||||
|
# see: https://github.com/MetaCubeX/go/commits/release-branch.go1.22/
|
||||||
|
# revert:
|
||||||
|
# 693def151adff1af707d82d28f55dba81ceb08e1: "crypto/rand,runtime: switch RtlGenRandom for ProcessPrng"
|
||||||
|
# 7c1157f9544922e96945196b47b95664b1e39108: "net: remove sysSocket fallback for Windows 7"
|
||||||
|
# 48042aa09c2f878c4faa576948b07fe625c4707a: "syscall: remove Windows 7 console handle workaround"
|
||||||
|
# a17d959debdb04cd550016a3501dd09d50cd62e7: "runtime: always use LoadLibraryEx to load system libraries"
|
||||||
|
- name: Revert Golang1.22 commit for Windows7/8
|
||||||
|
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.22' }}
|
||||||
|
run: |
|
||||||
|
cd $(go env GOROOT)
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/9779155f18b6556a034f7bb79fb7fb2aad1e26a9.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/ef0606261340e608017860b423ffae5c1ce78239.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/7f83badcb925a7e743188041cb6e561fc9b5b642.diff | patch --verbose -p 1
|
||||||
|
curl https://github.com/MetaCubeX/go/commit/83ff9782e024cb328b690cbf0da4e7848a327f4f.diff | patch --verbose -p 1
|
||||||
|
|
||||||
|
# modify from https://github.com/restic/restic/issues/4636#issuecomment-1896455557
|
||||||
|
- name: Revert Golang1.21 commit for Windows7/8
|
||||||
|
if: ${{ runner.os == 'Windows' && matrix.go-version == '1.21' }}
|
||||||
|
run: |
|
||||||
|
cd $(go env GOROOT)
|
||||||
|
curl https://github.com/golang/go/commit/9e43850a3298a9b8b1162ba0033d4c53f8637571.diff | patch --verbose -R -p 1
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test ./... -v -count=1
|
||||||
|
|
||||||
|
- name: Test with tag with_gvisor
|
||||||
|
run: go test ./... -v -count=1 -tags "with_gvisor"
|
|
@ -290,6 +290,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
|
||||||
t = uint16(time.Since(start) / time.Millisecond)
|
t = uint16(time.Since(start) / time.Millisecond)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||||
return &Proxy{
|
return &Proxy{
|
||||||
ProxyAdapter: adapter,
|
ProxyAdapter: adapter,
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/transport/socks5"
|
"github.com/metacubex/mihomo/transport/socks5"
|
||||||
)
|
)
|
||||||
|
@ -21,13 +20,13 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
|
||||||
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
|
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
|
||||||
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||||
case socks5.AtypIPv4:
|
case socks5.AtypIPv4:
|
||||||
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len]))
|
metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv4len])
|
||||||
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
|
||||||
case socks5.AtypIPv6:
|
case socks5.AtypIPv6:
|
||||||
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
||||||
metadata.DstIP = ip6.Unmap()
|
|
||||||
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||||
}
|
}
|
||||||
|
metadata.DstIP = metadata.DstIP.Unmap()
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -12,7 +11,6 @@ import (
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/transport/anytls"
|
"github.com/metacubex/mihomo/transport/anytls"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
@ -52,7 +50,7 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewConn(CN.NewRefConn(c, t), t), nil
|
return NewConn(c, t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
|
@ -73,7 +71,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
metadata.DstIP = ip
|
metadata.DstIP = ip
|
||||||
}
|
}
|
||||||
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
destination := M.SocksaddrFromNet(metadata.UDPAddr())
|
||||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), t), nil
|
return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
// SupportUOT implements C.ProxyAdapter
|
||||||
|
@ -88,6 +86,11 @@ func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements C.ProxyAdapter
|
||||||
|
func (t *AnyTLS) Close() error {
|
||||||
|
return t.client.Close()
|
||||||
|
}
|
||||||
|
|
||||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
|
@ -111,9 +114,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||||
if tlsConfig.Host == "" {
|
if tlsConfig.Host == "" {
|
||||||
tlsConfig.Host = option.Server
|
tlsConfig.Host = option.Server
|
||||||
}
|
}
|
||||||
if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
|
|
||||||
tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
tOption.TLSConfig = tlsConfig
|
tOption.TLSConfig = tlsConfig
|
||||||
|
|
||||||
outbound := &AnyTLS{
|
outbound := &AnyTLS{
|
||||||
|
@ -132,9 +132,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||||
option: &option,
|
option: &option,
|
||||||
dialer: singDialer,
|
dialer: singDialer,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
|
|
||||||
_ = o.client.Close()
|
|
||||||
})
|
|
||||||
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,23 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net"
|
"net"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/common/utils"
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ProxyAdapter interface {
|
||||||
|
C.ProxyAdapter
|
||||||
|
DialOptions(opts ...dialer.Option) []dialer.Option
|
||||||
|
}
|
||||||
|
|
||||||
type Base struct {
|
type Base struct {
|
||||||
name string
|
name string
|
||||||
addr string
|
addr string
|
||||||
|
@ -152,6 +160,10 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Base) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BasicOption struct {
|
type BasicOption struct {
|
||||||
TFO bool `proxy:"tfo,omitempty"`
|
TFO bool `proxy:"tfo,omitempty"`
|
||||||
MPTCP bool `proxy:"mptcp,omitempty"`
|
MPTCP bool `proxy:"mptcp,omitempty"`
|
||||||
|
@ -221,6 +233,10 @@ func (c *conn) ReaderReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *conn) AddRef(ref any) {
|
||||||
|
c.ExtendedConn = N.NewRefConn(c.ExtendedConn, ref) // add ref for autoCloseProxyAdapter
|
||||||
|
}
|
||||||
|
|
||||||
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
|
||||||
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
|
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
|
||||||
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
|
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
|
||||||
|
@ -267,6 +283,10 @@ func (c *packetConn) ReaderReplaceable() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *packetConn) AddRef(ref any) {
|
||||||
|
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
|
||||||
|
}
|
||||||
|
|
||||||
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
|
||||||
epc := N.NewEnhancePacketConn(pc)
|
epc := N.NewEnhancePacketConn(pc)
|
||||||
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
|
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
|
||||||
|
@ -286,3 +306,75 @@ func parseRemoteDestination(addr string) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddRef interface {
|
||||||
|
AddRef(ref any)
|
||||||
|
}
|
||||||
|
|
||||||
|
type autoCloseProxyAdapter struct {
|
||||||
|
ProxyAdapter
|
||||||
|
closeOnce sync.Once
|
||||||
|
closeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
c, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AddRef); ok {
|
||||||
|
c.AddRef(p)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *autoCloseProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
|
||||||
|
c, err := p.ProxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(AddRef); ok {
|
||||||
|
c.AddRef(p)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
|
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pc, ok := pc.(AddRef); ok {
|
||||||
|
pc.AddRef(p)
|
||||||
|
}
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *autoCloseProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
|
pc, err := p.ProxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if pc, ok := pc.(AddRef); ok {
|
||||||
|
pc.AddRef(p)
|
||||||
|
}
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *autoCloseProxyAdapter) Close() error {
|
||||||
|
p.closeOnce.Do(func() {
|
||||||
|
log.Debugln("Closing outdated proxy [%s]", p.Name())
|
||||||
|
runtime.SetFinalizer(p, nil)
|
||||||
|
p.closeErr = p.ProxyAdapter.Close()
|
||||||
|
})
|
||||||
|
return p.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAutoCloseProxyAdapter(adapter ProxyAdapter) ProxyAdapter {
|
||||||
|
proxy := &autoCloseProxyAdapter{
|
||||||
|
ProxyAdapter: adapter,
|
||||||
|
}
|
||||||
|
// auto close ProxyAdapter
|
||||||
|
runtime.SetFinalizer(proxy, (*autoCloseProxyAdapter).Close)
|
||||||
|
return proxy
|
||||||
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
|
@ -51,7 +51,7 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.shakeHand(metadata, c); err != nil {
|
if err := h.shakeHandContext(ctx, c, metadata); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
|
@ -99,7 +99,12 @@ func (h *Http) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
func (h *Http) shakeHandContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
|
||||||
addr := metadata.RemoteAddress()
|
addr := metadata.RemoteAddress()
|
||||||
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
||||||
tempHeaders := map[string]string{
|
tempHeaders := map[string]string{
|
||||||
|
@ -123,13 +128,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
||||||
|
|
||||||
HeaderString += "\r\n"
|
HeaderString += "\r\n"
|
||||||
|
|
||||||
_, err := rw.Write([]byte(HeaderString))
|
_, err = c.Write([]byte(HeaderString))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.ReadResponse(bufio.NewReader(rw), nil)
|
resp, err := http.ReadResponse(bufio.NewReader(c), nil)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,7 +14,6 @@ import (
|
||||||
"github.com/metacubex/quic-go/congestion"
|
"github.com/metacubex/quic-go/congestion"
|
||||||
M "github.com/sagernet/sing/common/metadata"
|
M "github.com/sagernet/sing/common/metadata"
|
||||||
|
|
||||||
CN "github.com/metacubex/mihomo/common/net"
|
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
|
@ -45,8 +43,6 @@ type Hysteria struct {
|
||||||
|
|
||||||
option *HysteriaOption
|
option *HysteriaOption
|
||||||
client *core.Client
|
client *core.Client
|
||||||
|
|
||||||
closeCh chan struct{} // for test
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
||||||
|
@ -55,7 +51,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(CN.NewRefConn(tcpConn, h), h), nil
|
return NewConn(tcpConn, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
||||||
|
@ -63,7 +59,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil
|
return newPacketConn(&hyPacketConn{udpConn}, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
|
||||||
|
@ -82,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
||||||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||||
},
|
},
|
||||||
remoteAddr: func(addr string) (net.Addr, error) {
|
remoteAddr: func(addr string) (net.Addr, error) {
|
||||||
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer)
|
return resolveUDPAddr(ctx, "udp", addr, h.prefer)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -239,18 +235,16 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
|
||||||
option: &option,
|
option: &option,
|
||||||
client: client,
|
client: client,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeHysteria)
|
|
||||||
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeHysteria(h *Hysteria) {
|
// Close implements C.ProxyAdapter
|
||||||
|
func (h *Hysteria) Close() error {
|
||||||
if h.client != nil {
|
if h.client != nil {
|
||||||
_ = h.client.Close()
|
return h.client.Close()
|
||||||
}
|
|
||||||
if h.closeCh != nil {
|
|
||||||
close(h.closeCh)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type hyPacketConn struct {
|
type hyPacketConn struct {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -39,8 +38,6 @@ type Hysteria2 struct {
|
||||||
option *Hysteria2Option
|
option *Hysteria2Option
|
||||||
client *hysteria2.Client
|
client *hysteria2.Client
|
||||||
dialer proxydialer.SingDialer
|
dialer proxydialer.SingDialer
|
||||||
|
|
||||||
closeCh chan struct{} // for test
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Hysteria2Option struct {
|
type Hysteria2Option struct {
|
||||||
|
@ -78,7 +75,7 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewConn(CN.NewRefConn(c, h), h), nil
|
return NewConn(c, h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
|
@ -91,16 +88,15 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||||
if pc == nil {
|
if pc == nil {
|
||||||
return nil, errors.New("packetConn is nil")
|
return nil, errors.New("packetConn is nil")
|
||||||
}
|
}
|
||||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil
|
return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeHysteria2(h *Hysteria2) {
|
// Close implements C.ProxyAdapter
|
||||||
|
func (h *Hysteria2) Close() error {
|
||||||
if h.client != nil {
|
if h.client != nil {
|
||||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
return h.client.CloseWithError(errors.New("proxy removed"))
|
||||||
}
|
|
||||||
if h.closeCh != nil {
|
|
||||||
close(h.closeCh)
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProxyInfo implements C.ProxyAdapter
|
// ProxyInfo implements C.ProxyAdapter
|
||||||
|
@ -175,7 +171,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||||
CWND: option.CWND,
|
CWND: option.CWND,
|
||||||
UdpMTU: option.UdpMTU,
|
UdpMTU: option.UdpMTU,
|
||||||
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
|
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
|
||||||
return resolveUDPAddrWithPrefer(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
|
return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +188,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||||
})
|
})
|
||||||
if len(serverAddress) > 0 {
|
if len(serverAddress) > 0 {
|
||||||
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
|
||||||
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.HopInterval == 0 {
|
if option.HopInterval == 0 {
|
||||||
|
@ -226,7 +222,6 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
||||||
client: client,
|
client: client,
|
||||||
dialer: singDialer,
|
dialer: singDialer,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeHysteria2)
|
|
||||||
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHysteria2GC(t *testing.T) {
|
|
||||||
option := Hysteria2Option{}
|
|
||||||
option.Server = "127.0.0.1"
|
|
||||||
option.Ports = "200,204,401-429,501-503"
|
|
||||||
option.HopInterval = 30
|
|
||||||
option.Password = "password"
|
|
||||||
option.Obfs = "salamander"
|
|
||||||
option.ObfsPassword = "password"
|
|
||||||
option.SNI = "example.com"
|
|
||||||
option.ALPN = []string{"h3"}
|
|
||||||
hy, err := NewHysteria2(option)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
closeCh := make(chan struct{})
|
|
||||||
hy.closeCh = closeCh
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
hy = nil
|
|
||||||
runtime.GC()
|
|
||||||
select {
|
|
||||||
case <-closeCh:
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
t.Error("timeout not GC")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHysteriaGC(t *testing.T) {
|
|
||||||
option := HysteriaOption{}
|
|
||||||
option.Server = "127.0.0.1"
|
|
||||||
option.Ports = "200,204,401-429,501-503"
|
|
||||||
option.Protocol = "udp"
|
|
||||||
option.Up = "1Mbps"
|
|
||||||
option.Down = "1Mbps"
|
|
||||||
option.HopInterval = 30
|
|
||||||
option.Obfs = "salamander"
|
|
||||||
option.SNI = "example.com"
|
|
||||||
option.ALPN = []string{"h3"}
|
|
||||||
hy, err := NewHysteria(option)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
closeCh := make(chan struct{})
|
|
||||||
hy.closeCh = closeCh
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
hy = nil
|
|
||||||
runtime.GC()
|
|
||||||
select {
|
|
||||||
case <-closeCh:
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
t.Error("timeout not GC")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -62,7 +61,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
|
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
|
||||||
}
|
}
|
||||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), m), nil
|
return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportUOT implements C.ProxyAdapter
|
// SupportUOT implements C.ProxyAdapter
|
||||||
|
@ -141,16 +140,17 @@ func NewMieru(option MieruOption) (*Mieru, error) {
|
||||||
option: &option,
|
option: &option,
|
||||||
client: c,
|
client: c,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeMieru)
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeMieru(m *Mieru) {
|
// Close implements C.ProxyAdapter
|
||||||
|
func (m *Mieru) Close() error {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
if m.client != nil && m.client.IsRunning() {
|
if m.client != nil && m.client.IsRunning() {
|
||||||
m.client.Stop()
|
return m.client.Stop()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
||||||
|
|
|
@ -20,16 +20,19 @@ func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
||||||
config := new(tlsC.RealityConfig)
|
config := new(tlsC.RealityConfig)
|
||||||
|
|
||||||
const x25519ScalarSize = 32
|
const x25519ScalarSize = 32
|
||||||
var publicKey [x25519ScalarSize]byte
|
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
|
||||||
n, err := base64.RawURLEncoding.Decode(publicKey[:], []byte(o.PublicKey))
|
if err != nil || len(publicKey) != x25519ScalarSize {
|
||||||
if err != nil || n != x25519ScalarSize {
|
|
||||||
return nil, errors.New("invalid REALITY public key")
|
return nil, errors.New("invalid REALITY public key")
|
||||||
}
|
}
|
||||||
config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey[:])
|
config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("fail to create REALITY public key: %w", err)
|
return nil, fmt.Errorf("fail to create REALITY public key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
n := hex.DecodedLen(len(o.ShortID))
|
||||||
|
if n > tlsC.RealityMaxShortIDLen {
|
||||||
|
return nil, errors.New("invalid REALITY short id")
|
||||||
|
}
|
||||||
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
|
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
|
||||||
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
||||||
return nil, errors.New("invalid REALITY short ID")
|
return nil, errors.New("invalid REALITY short ID")
|
||||||
|
|
|
@ -100,7 +100,7 @@ type restlsOption struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
useEarly := false
|
useEarly := false
|
||||||
switch ss.obfsMode {
|
switch ss.obfsMode {
|
||||||
case "tls":
|
case "tls":
|
||||||
|
@ -109,7 +109,6 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||||
_, port, _ := net.SplitHostPort(ss.addr)
|
_, port, _ := net.SplitHostPort(ss.addr)
|
||||||
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
||||||
case "websocket":
|
case "websocket":
|
||||||
var err error
|
|
||||||
if ss.v2rayOption != nil {
|
if ss.v2rayOption != nil {
|
||||||
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
|
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
|
||||||
} else if ss.gostOption != nil {
|
} else if ss.gostOption != nil {
|
||||||
|
@ -121,14 +120,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||||
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
|
||||||
}
|
}
|
||||||
case shadowtls.Mode:
|
case shadowtls.Mode:
|
||||||
var err error
|
|
||||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
useEarly = true
|
useEarly = true
|
||||||
case restls.Mode:
|
case restls.Mode:
|
||||||
var err error
|
|
||||||
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
|
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
|
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
|
||||||
|
@ -136,6 +133,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
||||||
useEarly = true
|
useEarly = true
|
||||||
}
|
}
|
||||||
useEarly = useEarly || N.NeedHandshake(c)
|
useEarly = useEarly || N.NeedHandshake(c)
|
||||||
|
if !useEarly {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
|
||||||
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
|
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
|
||||||
if useEarly {
|
if useEarly {
|
||||||
|
@ -197,7 +200,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
|
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,12 +42,15 @@ type ShadowSocksROption struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
c = ssr.obfs.StreamConn(c)
|
c = ssr.obfs.StreamConn(c)
|
||||||
c = ssr.cipher.StreamConn(c)
|
c = ssr.cipher.StreamConn(c)
|
||||||
var (
|
var (
|
||||||
iv []byte
|
iv []byte
|
||||||
err error
|
|
||||||
)
|
)
|
||||||
switch conn := c.(type) {
|
switch conn := c.(type) {
|
||||||
case *shadowstream.Conn:
|
case *shadowstream.Conn:
|
||||||
|
@ -102,7 +105,7 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package outbound
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"runtime"
|
|
||||||
|
|
||||||
CN "github.com/metacubex/mihomo/common/net"
|
CN "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
|
@ -18,8 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SingMux struct {
|
type SingMux struct {
|
||||||
C.ProxyAdapter
|
ProxyAdapter
|
||||||
base ProxyBase
|
|
||||||
client *mux.Client
|
client *mux.Client
|
||||||
dialer proxydialer.SingDialer
|
dialer proxydialer.SingDialer
|
||||||
onlyTcp bool
|
onlyTcp bool
|
||||||
|
@ -43,25 +41,21 @@ type BrutalOption struct {
|
||||||
Down string `proxy:"down,omitempty"`
|
Down string `proxy:"down,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyBase interface {
|
|
||||||
DialOptions(opts ...dialer.Option) []dialer.Option
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
options := s.base.DialOptions(opts...)
|
options := s.ProxyAdapter.DialOptions(opts...)
|
||||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err
|
return NewConn(c, s), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
if s.onlyTcp {
|
if s.onlyTcp {
|
||||||
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||||
}
|
}
|
||||||
options := s.base.DialOptions(opts...)
|
options := s.ProxyAdapter.DialOptions(opts...)
|
||||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||||
|
|
||||||
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||||
|
@ -80,7 +74,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
if pc == nil {
|
if pc == nil {
|
||||||
return nil, E.New("packetConn is nil")
|
return nil, E.New("packetConn is nil")
|
||||||
}
|
}
|
||||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil
|
return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SingMux) SupportUDP() bool {
|
func (s *SingMux) SupportUDP() bool {
|
||||||
|
@ -103,11 +97,15 @@ func (s *SingMux) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeSingMux(s *SingMux) {
|
// Close implements C.ProxyAdapter
|
||||||
_ = s.client.Close()
|
func (s *SingMux) Close() error {
|
||||||
|
if s.client != nil {
|
||||||
|
_ = s.client.Close()
|
||||||
|
}
|
||||||
|
return s.ProxyAdapter.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) {
|
func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error) {
|
||||||
// TODO
|
// TODO
|
||||||
// "TCP Brutal is only supported on Linux-based systems"
|
// "TCP Brutal is only supported on Linux-based systems"
|
||||||
|
|
||||||
|
@ -131,11 +129,9 @@ func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.P
|
||||||
}
|
}
|
||||||
outbound := &SingMux{
|
outbound := &SingMux{
|
||||||
ProxyAdapter: proxy,
|
ProxyAdapter: proxy,
|
||||||
base: base,
|
|
||||||
client: client,
|
client: client,
|
||||||
dialer: singDialer,
|
dialer: singDialer,
|
||||||
onlyTcp: option.OnlyTcp,
|
onlyTcp: option.OnlyTcp,
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeSingMux)
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/common/structure"
|
"github.com/metacubex/mihomo/common/structure"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
|
@ -41,7 +42,7 @@ type streamOption struct {
|
||||||
obfsOption *simpleObfsOption
|
obfsOption *simpleObfsOption
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
func snellStreamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||||
switch option.obfsOption.Mode {
|
switch option.obfsOption.Mode {
|
||||||
case "tls":
|
case "tls":
|
||||||
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
||||||
|
@ -54,25 +55,35 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
c = snellStreamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||||
if metadata.NetWork == C.UDP {
|
err := s.writeHeaderContext(ctx, c, metadata)
|
||||||
err := snell.WriteUDPHeader(c, s.version)
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
|
|
||||||
return c, err
|
return c, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Snell) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if metadata.NetWork == C.UDP {
|
||||||
|
err = snell.WriteUDPHeader(c, s.version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
if s.version == snell.Version2 && len(opts) == 0 {
|
if s.version == snell.Version2 && dialer.IsZeroOptions(opts) {
|
||||||
c, err := s.pool.Get()
|
c, err := s.pool.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
|
if err = s.writeHeaderContext(ctx, c, metadata); err != nil {
|
||||||
c.Close()
|
_ = c.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewConn(c, s), err
|
return NewConn(c, s), err
|
||||||
|
@ -120,12 +131,8 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
|
||||||
|
|
||||||
err = snell.WriteUDPHeader(c, s.version)
|
c, err = s.StreamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
pc := snell.PacketConn(c)
|
pc := snell.PacketConn(c)
|
||||||
return newPacketConn(pc, s), nil
|
return newPacketConn(pc, s), nil
|
||||||
|
@ -212,7 +219,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
return snellStreamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
|
@ -58,7 +59,7 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C
|
||||||
Password: ss.pass,
|
Password: ss.pass,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
if _, err := ss.clientHandshakeContext(ctx, c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return c, nil
|
return c, nil
|
||||||
|
@ -135,7 +136,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
|
||||||
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
|
bindAddr, err := ss.clientHandshakeContext(ctx, c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("client hanshake error: %w", err)
|
err = fmt.Errorf("client hanshake error: %w", err)
|
||||||
return
|
return
|
||||||
|
@ -147,7 +148,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
err = errors.New("invalid UDP bind address")
|
err = errors.New("invalid UDP bind address")
|
||||||
return
|
return
|
||||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||||
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr())
|
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr(), C.IPv4Prefer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -178,6 +179,14 @@ func (ss *Socks5) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr socks5.Addr, command socks5.Command, user *socks5.User) (_ socks5.Addr, err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
return socks5.ClientHandshake(c, addr, command, user)
|
||||||
|
}
|
||||||
|
|
||||||
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
func NewSocks5(option Socks5Option) (*Socks5, error) {
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -25,7 +24,10 @@ type Ssh struct {
|
||||||
*Base
|
*Base
|
||||||
|
|
||||||
option *SshOption
|
option *SshOption
|
||||||
client *sshClient // using a standalone struct to avoid its inner loop invalidate the Finalizer
|
|
||||||
|
config *ssh.ClientConfig
|
||||||
|
client *ssh.Client
|
||||||
|
cMutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type SshOption struct {
|
type SshOption struct {
|
||||||
|
@ -49,7 +51,7 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
client, err := s.client.connect(ctx, cDialer, s.addr)
|
client, err := s.connect(ctx, cDialer, s.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -58,16 +60,10 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewConn(N.NewRefConn(c, s), s), nil
|
return NewConn(c, s), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type sshClient struct {
|
func (s *Ssh) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
|
||||||
config *ssh.ClientConfig
|
|
||||||
client *ssh.Client
|
|
||||||
cMutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
|
|
||||||
s.cMutex.Lock()
|
s.cMutex.Lock()
|
||||||
defer s.cMutex.Unlock()
|
defer s.cMutex.Unlock()
|
||||||
if s.client != nil {
|
if s.client != nil {
|
||||||
|
@ -108,7 +104,15 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sshClient) Close() error {
|
// ProxyInfo implements C.ProxyAdapter
|
||||||
|
func (s *Ssh) ProxyInfo() C.ProxyInfo {
|
||||||
|
info := s.Base.ProxyInfo()
|
||||||
|
info.DialerProxy = s.option.DialerProxy
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements C.ProxyAdapter
|
||||||
|
func (s *Ssh) Close() error {
|
||||||
s.cMutex.Lock()
|
s.cMutex.Lock()
|
||||||
defer s.cMutex.Unlock()
|
defer s.cMutex.Unlock()
|
||||||
if s.client != nil {
|
if s.client != nil {
|
||||||
|
@ -117,17 +121,6 @@ func (s *sshClient) Close() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeSsh(s *Ssh) {
|
|
||||||
_ = s.client.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProxyInfo implements C.ProxyAdapter
|
|
||||||
func (s *Ssh) ProxyInfo() C.ProxyInfo {
|
|
||||||
info := s.Base.ProxyInfo()
|
|
||||||
info.DialerProxy = s.option.DialerProxy
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSsh(option SshOption) (*Ssh, error) {
|
func NewSsh(option SshOption) (*Ssh, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
|
@ -204,11 +197,8 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
option: &option,
|
option: &option,
|
||||||
client: &sshClient{
|
config: &config,
|
||||||
config: &config,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeSsh)
|
|
||||||
|
|
||||||
return outbound, nil
|
return outbound, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
"github.com/metacubex/mihomo/component/ca"
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
|
@ -17,12 +18,13 @@ import (
|
||||||
"github.com/metacubex/mihomo/transport/gun"
|
"github.com/metacubex/mihomo/transport/gun"
|
||||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||||
"github.com/metacubex/mihomo/transport/trojan"
|
"github.com/metacubex/mihomo/transport/trojan"
|
||||||
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Trojan struct {
|
type Trojan struct {
|
||||||
*Base
|
*Base
|
||||||
instance *trojan.Trojan
|
option *TrojanOption
|
||||||
option *TrojanOption
|
hexPassword [trojan.KeyLength]byte
|
||||||
|
|
||||||
// for gun mux
|
// for gun mux
|
||||||
gunTLSConfig *tls.Config
|
gunTLSConfig *tls.Config
|
||||||
|
@ -60,15 +62,21 @@ type TrojanSSOption struct {
|
||||||
Password string `proxy:"password,omitempty"`
|
Password string `proxy:"password,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
if t.option.Network == "ws" {
|
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
|
switch t.option.Network {
|
||||||
|
case "ws":
|
||||||
host, port, _ := net.SplitHostPort(t.addr)
|
host, port, _ := net.SplitHostPort(t.addr)
|
||||||
wsOpts := &trojan.WebsocketOption{
|
|
||||||
|
wsOpts := &vmess.WebsocketConfig{
|
||||||
Host: host,
|
Host: host,
|
||||||
Port: port,
|
Port: port,
|
||||||
Path: t.option.WSOpts.Path,
|
Path: t.option.WSOpts.Path,
|
||||||
|
MaxEarlyData: t.option.WSOpts.MaxEarlyData,
|
||||||
|
EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
|
||||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||||
|
ClientFingerprint: t.option.ClientFingerprint,
|
||||||
Headers: http.Header{},
|
Headers: http.Header{},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,57 +90,95 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.instance.StreamWebsocketConn(ctx, c, wsOpts)
|
alpn := trojan.DefaultWebsocketALPN
|
||||||
}
|
if len(t.option.ALPN) != 0 {
|
||||||
|
alpn = t.option.ALPN
|
||||||
|
}
|
||||||
|
|
||||||
return t.instance.StreamConn(ctx, c)
|
wsOpts.TLS = true
|
||||||
}
|
tlsConfig := &tls.Config{
|
||||||
|
NextProtos: alpn,
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
|
InsecureSkipVerify: t.option.SkipCertVerify,
|
||||||
|
ServerName: t.option.SNI,
|
||||||
|
}
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
|
||||||
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
|
|
||||||
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.transport != nil {
|
|
||||||
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
|
||||||
} else {
|
|
||||||
c, err = t.plainStream(ctx, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.ssCipher != nil {
|
|
||||||
c = t.ssCipher.StreamConn(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if metadata.NetWork == C.UDP {
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
|
|
||||||
return c, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
|
||||||
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
|
||||||
// gun transport
|
|
||||||
if t.transport != nil && len(opts) == 0 {
|
|
||||||
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.ssCipher != nil {
|
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||||
c = t.ssCipher.StreamConn(c)
|
case "grpc":
|
||||||
|
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
|
||||||
|
default:
|
||||||
|
// default tcp network
|
||||||
|
// handle TLS
|
||||||
|
alpn := trojan.DefaultALPN
|
||||||
|
if len(t.option.ALPN) != 0 {
|
||||||
|
alpn = t.option.ALPN
|
||||||
}
|
}
|
||||||
|
c, err = vmess.StreamTLSConn(ctx, c, &vmess.TLSConfig{
|
||||||
|
Host: t.option.SNI,
|
||||||
|
SkipCertVerify: t.option.SkipCertVerify,
|
||||||
|
FingerPrint: t.option.Fingerprint,
|
||||||
|
ClientFingerprint: t.option.ClientFingerprint,
|
||||||
|
NextProtos: alpn,
|
||||||
|
Reality: t.realityConfig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
}
|
||||||
|
|
||||||
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil {
|
return t.streamConnContext(ctx, c, metadata)
|
||||||
c.Close()
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
|
if t.ssCipher != nil {
|
||||||
|
c = t.ssCipher.StreamConn(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
command := trojan.CommandTCP
|
||||||
|
if metadata.NetWork == C.UDP {
|
||||||
|
command = trojan.CommandUDP
|
||||||
|
}
|
||||||
|
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
command := trojan.CommandTCP
|
||||||
|
if metadata.NetWork == C.UDP {
|
||||||
|
command = trojan.CommandUDP
|
||||||
|
}
|
||||||
|
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DialContext implements C.ProxyAdapter
|
||||||
|
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
var c net.Conn
|
||||||
|
// gun transport
|
||||||
|
if t.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
|
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer func(c net.Conn) {
|
||||||
|
safeConnClose(c, err)
|
||||||
|
}(c)
|
||||||
|
|
||||||
|
c, err = t.streamConnContext(ctx, c, metadata)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +217,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
|
|
||||||
// grpc transport
|
// grpc transport
|
||||||
if t.transport != nil && len(opts) == 0 {
|
if t.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
||||||
|
@ -180,16 +226,12 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
if t.ssCipher != nil {
|
c, err = t.streamConnContext(ctx, c, metadata)
|
||||||
c = t.ssCipher.StreamConn(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := t.instance.PacketConn(c)
|
pc := trojan.NewPacketConn(c)
|
||||||
return newPacketConn(pc, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
|
||||||
|
@ -210,21 +252,12 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
|
||||||
defer func(c net.Conn) {
|
defer func(c net.Conn) {
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
c, err = t.plainStream(ctx, c)
|
c, err = t.StreamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.ssCipher != nil {
|
|
||||||
c = t.ssCipher.StreamConn(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
pc := t.instance.PacketConn(c)
|
pc := trojan.NewPacketConn(c)
|
||||||
return newPacketConn(pc, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +268,7 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
pc := t.instance.PacketConn(c)
|
pc := trojan.NewPacketConn(c)
|
||||||
return newPacketConn(pc, t), err
|
return newPacketConn(pc, t), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,22 +284,17 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements C.ProxyAdapter
|
||||||
|
func (t *Trojan) Close() error {
|
||||||
|
if t.transport != nil {
|
||||||
|
return t.transport.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||||
|
|
||||||
tOption := &trojan.Option{
|
|
||||||
Password: option.Password,
|
|
||||||
ALPN: option.ALPN,
|
|
||||||
ServerName: option.Server,
|
|
||||||
SkipCertVerify: option.SkipCertVerify,
|
|
||||||
Fingerprint: option.Fingerprint,
|
|
||||||
ClientFingerprint: option.ClientFingerprint,
|
|
||||||
}
|
|
||||||
|
|
||||||
if option.SNI != "" {
|
|
||||||
tOption.ServerName = option.SNI
|
|
||||||
}
|
|
||||||
|
|
||||||
t := &Trojan{
|
t := &Trojan{
|
||||||
Base: &Base{
|
Base: &Base{
|
||||||
name: option.Name,
|
name: option.Name,
|
||||||
|
@ -279,8 +307,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
rmark: option.RoutingMark,
|
rmark: option.RoutingMark,
|
||||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||||
},
|
},
|
||||||
instance: trojan.New(tOption),
|
option: &option,
|
||||||
option: &option,
|
hexPassword: trojan.Key(option.Password),
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -288,7 +316,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tOption.Reality = t.realityConfig
|
|
||||||
|
|
||||||
if option.SSOpts.Enabled {
|
if option.SSOpts.Enabled {
|
||||||
if option.SSOpts.Password == "" {
|
if option.SSOpts.Password == "" {
|
||||||
|
@ -305,7 +332,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if option.Network == "grpc" {
|
if option.Network == "grpc" {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
|
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
|
||||||
if len(t.option.DialerProxy) > 0 {
|
if len(t.option.DialerProxy) > 0 {
|
||||||
|
@ -314,7 +341,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
|
c, err := cDialer.DialContext(ctx, "tcp", t.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -324,8 +351,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
NextProtos: option.ALPN,
|
NextProtos: option.ALPN,
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
InsecureSkipVerify: tOption.SkipCertVerify,
|
InsecureSkipVerify: option.SkipCertVerify,
|
||||||
ServerName: tOption.ServerName,
|
ServerName: option.SNI,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
@ -334,12 +361,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig)
|
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
|
||||||
|
|
||||||
t.gunTLSConfig = tlsConfig
|
t.gunTLSConfig = tlsConfig
|
||||||
t.gunConfig = &gun.Config{
|
t.gunConfig = &gun.Config{
|
||||||
ServiceName: option.GrpcOpts.GrpcServiceName,
|
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||||
Host: tOption.ServerName,
|
Host: option.SNI,
|
||||||
|
ClientFingerprint: option.ClientFingerprint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
|
udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,118 +3,59 @@ package outbound
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/transport/socks5"
|
"github.com/metacubex/mihomo/transport/socks5"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
globalClientSessionCache tls.ClientSessionCache
|
|
||||||
once sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func getClientSessionCache() tls.ClientSessionCache {
|
|
||||||
once.Do(func() {
|
|
||||||
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
|
|
||||||
})
|
|
||||||
return globalClientSessionCache
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
func serializesSocksAddr(metadata *C.Metadata) []byte {
|
||||||
var buf [][]byte
|
var buf [][]byte
|
||||||
addrType := metadata.AddrType()
|
addrType := metadata.AddrType()
|
||||||
aType := uint8(addrType)
|
|
||||||
p := uint(metadata.DstPort)
|
p := uint(metadata.DstPort)
|
||||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||||
switch addrType {
|
switch addrType {
|
||||||
case socks5.AtypDomainName:
|
case C.AtypDomainName:
|
||||||
lenM := uint8(len(metadata.Host))
|
lenM := uint8(len(metadata.Host))
|
||||||
host := []byte(metadata.Host)
|
host := []byte(metadata.Host)
|
||||||
buf = [][]byte{{aType, lenM}, host, port}
|
buf = [][]byte{{socks5.AtypDomainName, lenM}, host, port}
|
||||||
case socks5.AtypIPv4:
|
case C.AtypIPv4:
|
||||||
host := metadata.DstIP.AsSlice()
|
host := metadata.DstIP.AsSlice()
|
||||||
buf = [][]byte{{aType}, host, port}
|
buf = [][]byte{{socks5.AtypIPv4}, host, port}
|
||||||
case socks5.AtypIPv6:
|
case C.AtypIPv6:
|
||||||
host := metadata.DstIP.AsSlice()
|
host := metadata.DstIP.AsSlice()
|
||||||
buf = [][]byte{{aType}, host, port}
|
buf = [][]byte{{socks5.AtypIPv6}, host, port}
|
||||||
}
|
}
|
||||||
return bytes.Join(buf, nil)
|
return bytes.Join(buf, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, error) {
|
func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
|
||||||
host, port, err := net.SplitHostPort(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
|
|
||||||
host, port, err := net.SplitHostPort(address)
|
host, port, err := net.SplitHostPort(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var ip netip.Addr
|
var ip netip.Addr
|
||||||
var fallback netip.Addr
|
|
||||||
switch prefer {
|
switch prefer {
|
||||||
case C.IPv4Only:
|
case C.IPv4Only:
|
||||||
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
case C.IPv6Only:
|
case C.IPv6Only:
|
||||||
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
case C.IPv6Prefer:
|
case C.IPv6Prefer:
|
||||||
var ips []netip.Addr
|
ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
|
||||||
if err == nil {
|
|
||||||
for _, addr := range ips {
|
|
||||||
if addr.Is6() {
|
|
||||||
ip = addr
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
if !fallback.IsValid() {
|
|
||||||
fallback = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
// C.IPv4Prefer, C.DualStack and other
|
ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||||
var ips []netip.Addr
|
|
||||||
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
|
||||||
if err == nil {
|
|
||||||
for _, addr := range ips {
|
|
||||||
if addr.Is4() {
|
|
||||||
ip = addr
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
if !fallback.IsValid() {
|
|
||||||
fallback = addr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ip.IsValid() && fallback.IsValid() {
|
|
||||||
ip = fallback
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ip, port = resolver.LookupIP4P(ip, port)
|
||||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,6 @@ import (
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/transport/gun"
|
"github.com/metacubex/mihomo/transport/gun"
|
||||||
"github.com/metacubex/mihomo/transport/socks5"
|
|
||||||
"github.com/metacubex/mihomo/transport/vless"
|
"github.com/metacubex/mihomo/transport/vless"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
|
||||||
|
@ -76,13 +75,7 @@ type VlessOption struct {
|
||||||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
|
|
||||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
@ -156,7 +149,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||||
Path: v.option.HTTP2Opts.Path,
|
Path: v.option.HTTP2Opts.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
|
@ -169,10 +162,14 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.streamConn(c, metadata)
|
return v.streamConnContext(ctx, c, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
if metadata.NetWork == C.UDP {
|
if metadata.NetWork == C.UDP {
|
||||||
if v.option.PacketAddr {
|
if v.option.PacketAddr {
|
||||||
metadata = &C.Metadata{
|
metadata = &C.Metadata{
|
||||||
|
@ -229,9 +226,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -239,7 +237,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
c, err = v.streamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -284,7 +282,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
}
|
}
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -293,7 +291,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.streamConn(c, metadata)
|
c, err = v.streamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -385,19 +383,27 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements C.ProxyAdapter
|
||||||
|
func (v *Vless) Close() error {
|
||||||
|
if v.transport != nil {
|
||||||
|
return v.transport.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
|
||||||
var addrType byte
|
var addrType byte
|
||||||
var addr []byte
|
var addr []byte
|
||||||
switch metadata.AddrType() {
|
switch metadata.AddrType() {
|
||||||
case socks5.AtypIPv4:
|
case C.AtypIPv4:
|
||||||
addrType = vless.AtypIPv4
|
addrType = vless.AtypIPv4
|
||||||
addr = make([]byte, net.IPv4len)
|
addr = make([]byte, net.IPv4len)
|
||||||
copy(addr[:], metadata.DstIP.AsSlice())
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
case socks5.AtypIPv6:
|
case C.AtypIPv6:
|
||||||
addrType = vless.AtypIPv6
|
addrType = vless.AtypIPv6
|
||||||
addr = make([]byte, net.IPv6len)
|
addr = make([]byte, net.IPv6len)
|
||||||
copy(addr[:], metadata.DstIP.AsSlice())
|
copy(addr[:], metadata.DstIP.AsSlice())
|
||||||
case socks5.AtypDomainName:
|
case C.AtypDomainName:
|
||||||
addrType = vless.AtypDomainName
|
addrType = vless.AtypDomainName
|
||||||
addr = make([]byte, len(metadata.Host)+1)
|
addr = make([]byte, len(metadata.Host)+1)
|
||||||
addr[0] = byte(len(metadata.Host))
|
addr[0] = byte(len(metadata.Host))
|
||||||
|
@ -563,7 +569,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||||
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
||||||
if len(v.option.DialerProxy) > 0 {
|
if len(v.option.DialerProxy) > 0 {
|
||||||
|
@ -572,7 +578,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
|
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -589,10 +595,13 @@ func NewVless(option VlessOption) (*Vless, error) {
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
ServerName: v.option.ServerName,
|
ServerName: v.option.ServerName,
|
||||||
})
|
}, v.option.Fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if option.ServerName == "" {
|
if option.ServerName == "" {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
tlsConfig.ServerName = host
|
tlsConfig.ServerName = host
|
||||||
|
|
|
@ -96,13 +96,7 @@ type WSOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamConnContext implements C.ProxyAdapter
|
// StreamConnContext implements C.ProxyAdapter
|
||||||
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
|
|
||||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch v.option.Network {
|
switch v.option.Network {
|
||||||
case "ws":
|
case "ws":
|
||||||
host, port, _ := net.SplitHostPort(v.addr)
|
host, port, _ := net.SplitHostPort(v.addr)
|
||||||
|
@ -199,7 +193,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||||
Path: v.option.HTTP2Opts.Path,
|
Path: v.option.HTTP2Opts.Path,
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err = mihomoVMess.StreamH2Conn(c, h2Opts)
|
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
||||||
case "grpc":
|
case "grpc":
|
||||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||||
default:
|
default:
|
||||||
|
@ -226,17 +220,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return v.streamConn(c, metadata)
|
return v.streamConnContext(ctx, c, metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
|
||||||
|
useEarly := N.NeedHandshake(c)
|
||||||
|
if !useEarly {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, c)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
}
|
||||||
if metadata.NetWork == C.UDP {
|
if metadata.NetWork == C.UDP {
|
||||||
if v.option.XUDP {
|
if v.option.XUDP {
|
||||||
var globalID [8]byte
|
var globalID [8]byte
|
||||||
if metadata.SourceValid() {
|
if metadata.SourceValid() {
|
||||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||||
}
|
}
|
||||||
if N.NeedHandshake(c) {
|
if useEarly {
|
||||||
conn = v.client.DialEarlyXUDPPacketConn(c,
|
conn = v.client.DialEarlyXUDPPacketConn(c,
|
||||||
globalID,
|
globalID,
|
||||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||||
|
@ -246,7 +247,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||||
}
|
}
|
||||||
} else if v.option.PacketAddr {
|
} else if v.option.PacketAddr {
|
||||||
if N.NeedHandshake(c) {
|
if useEarly {
|
||||||
conn = v.client.DialEarlyPacketConn(c,
|
conn = v.client.DialEarlyPacketConn(c,
|
||||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||||
} else {
|
} else {
|
||||||
|
@ -255,7 +256,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||||
}
|
}
|
||||||
conn = packetaddr.NewBindConn(conn)
|
conn = packetaddr.NewBindConn(conn)
|
||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
if useEarly {
|
||||||
conn = v.client.DialEarlyPacketConn(c,
|
conn = v.client.DialEarlyPacketConn(c,
|
||||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||||
} else {
|
} else {
|
||||||
|
@ -264,7 +265,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if N.NeedHandshake(c) {
|
if useEarly {
|
||||||
conn = v.client.DialEarlyConn(c,
|
conn = v.client.DialEarlyConn(c,
|
||||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||||
} else {
|
} else {
|
||||||
|
@ -280,9 +281,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
||||||
|
|
||||||
// DialContext implements C.ProxyAdapter
|
// DialContext implements C.ProxyAdapter
|
||||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -290,7 +292,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
c, err = v.streamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -332,7 +334,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
}
|
}
|
||||||
var c net.Conn
|
var c net.Conn
|
||||||
// gun transport
|
// gun transport
|
||||||
if v.transport != nil && len(opts) == 0 {
|
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -341,7 +343,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
||||||
safeConnClose(c, err)
|
safeConnClose(c, err)
|
||||||
}(c)
|
}(c)
|
||||||
|
|
||||||
c, err = v.streamConn(c, metadata)
|
c, err = v.streamConnContext(ctx, c, metadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -395,6 +397,14 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close implements C.ProxyAdapter
|
||||||
|
func (v *Vmess) Close() error {
|
||||||
|
if v.transport != nil {
|
||||||
|
return v.transport.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||||
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
|
||||||
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
|
||||||
|
@ -470,7 +480,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||||
}
|
}
|
||||||
case "grpc":
|
case "grpc":
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
var err error
|
var err error
|
||||||
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
||||||
if len(v.option.DialerProxy) > 0 {
|
if len(v.option.DialerProxy) > 0 {
|
||||||
|
@ -479,7 +489,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
|
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -496,10 +506,13 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
||||||
}
|
}
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if option.TLS {
|
if option.TLS {
|
||||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||||
ServerName: v.option.ServerName,
|
ServerName: v.option.ServerName,
|
||||||
})
|
}, v.option.Fingerprint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if option.ServerName == "" {
|
if option.ServerName == "" {
|
||||||
host, _, _ := net.SplitHostPort(v.addr)
|
host, _, _ := net.SplitHostPort(v.addr)
|
||||||
tlsConfig.ServerName = host
|
tlsConfig.ServerName = host
|
||||||
|
|
|
@ -8,14 +8,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"runtime"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/atomic"
|
"github.com/metacubex/mihomo/common/atomic"
|
||||||
CN "github.com/metacubex/mihomo/common/net"
|
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/proxydialer"
|
"github.com/metacubex/mihomo/component/proxydialer"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
|
@ -45,7 +43,6 @@ type WireGuard struct {
|
||||||
tunDevice wireguard.Device
|
tunDevice wireguard.Device
|
||||||
dialer proxydialer.SingDialer
|
dialer proxydialer.SingDialer
|
||||||
resolver resolver.Resolver
|
resolver resolver.Resolver
|
||||||
refP *refProxyAdapter
|
|
||||||
|
|
||||||
initOk atomic.Bool
|
initOk atomic.Bool
|
||||||
initMutex sync.Mutex
|
initMutex sync.Mutex
|
||||||
|
@ -57,8 +54,6 @@ type WireGuard struct {
|
||||||
serverAddrMap map[M.Socksaddr]netip.AddrPort
|
serverAddrMap map[M.Socksaddr]netip.AddrPort
|
||||||
serverAddrTime atomic.TypedValue[time.Time]
|
serverAddrTime atomic.TypedValue[time.Time]
|
||||||
serverAddrMutex sync.Mutex
|
serverAddrMutex sync.Mutex
|
||||||
|
|
||||||
closeCh chan struct{} // for test
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WireGuardOption struct {
|
type WireGuardOption struct {
|
||||||
|
@ -173,7 +168,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||||
},
|
},
|
||||||
dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
|
dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
|
||||||
}
|
}
|
||||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
|
||||||
|
|
||||||
var reserved [3]uint8
|
var reserved [3]uint8
|
||||||
if len(option.Reserved) > 0 {
|
if len(option.Reserved) > 0 {
|
||||||
|
@ -286,15 +280,13 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
refP := &refProxyAdapter{}
|
|
||||||
outbound.refP = refP
|
|
||||||
if option.RemoteDnsResolve && len(option.Dns) > 0 {
|
if option.RemoteDnsResolve && len(option.Dns) > 0 {
|
||||||
nss, err := dns.ParseNameServer(option.Dns)
|
nss, err := dns.ParseNameServer(option.Dns)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for i := range nss {
|
for i := range nss {
|
||||||
nss[i].ProxyAdapter = refP
|
nss[i].ProxyAdapter = outbound
|
||||||
}
|
}
|
||||||
outbound.resolver = dns.NewResolver(dns.Config{
|
outbound.resolver = dns.NewResolver(dns.Config{
|
||||||
Main: nss,
|
Main: nss,
|
||||||
|
@ -309,7 +301,7 @@ func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.Add
|
||||||
if address.Addr.IsValid() {
|
if address.Addr.IsValid() {
|
||||||
return address.AddrPort(), nil
|
return address.AddrPort(), nil
|
||||||
}
|
}
|
||||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer)
|
udpAddr, err := resolveUDPAddr(ctx, "udp", address.String(), w.prefer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return netip.AddrPort{}, err
|
return netip.AddrPort{}, err
|
||||||
}
|
}
|
||||||
|
@ -488,13 +480,12 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
||||||
return ipcConf, nil
|
return ipcConf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeWireGuard(w *WireGuard) {
|
// Close implements C.ProxyAdapter
|
||||||
|
func (w *WireGuard) Close() error {
|
||||||
if w.device != nil {
|
if w.device != nil {
|
||||||
w.device.Close()
|
w.device.Close()
|
||||||
}
|
}
|
||||||
if w.closeCh != nil {
|
return nil
|
||||||
close(w.closeCh)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||||
|
@ -507,8 +498,6 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||||
if !metadata.Resolved() || w.resolver != nil {
|
if !metadata.Resolved() || w.resolver != nil {
|
||||||
r := resolver.DefaultResolver
|
r := resolver.DefaultResolver
|
||||||
if w.resolver != nil {
|
if w.resolver != nil {
|
||||||
w.refP.SetProxyAdapter(w)
|
|
||||||
defer w.refP.ClearProxyAdapter()
|
|
||||||
r = w.resolver
|
r = w.resolver
|
||||||
}
|
}
|
||||||
options = append(options, dialer.WithResolver(r))
|
options = append(options, dialer.WithResolver(r))
|
||||||
|
@ -523,7 +512,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
return nil, E.New("conn is nil")
|
return nil, E.New("conn is nil")
|
||||||
}
|
}
|
||||||
return NewConn(CN.NewRefConn(conn, w), w), nil
|
return NewConn(conn, w), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
|
||||||
|
@ -536,8 +525,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||||
r := resolver.DefaultResolver
|
r := resolver.DefaultResolver
|
||||||
if w.resolver != nil {
|
if w.resolver != nil {
|
||||||
w.refP.SetProxyAdapter(w)
|
|
||||||
defer w.refP.ClearProxyAdapter()
|
|
||||||
r = w.resolver
|
r = w.resolver
|
||||||
}
|
}
|
||||||
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
|
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
|
||||||
|
@ -553,139 +540,10 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
||||||
if pc == nil {
|
if pc == nil {
|
||||||
return nil, E.New("packetConn is nil")
|
return nil, E.New("packetConn is nil")
|
||||||
}
|
}
|
||||||
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
|
return newPacketConn(pc, w), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsL3Protocol implements C.ProxyAdapter
|
// IsL3Protocol implements C.ProxyAdapter
|
||||||
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
|
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
type refProxyAdapter struct {
|
|
||||||
proxyAdapter C.ProxyAdapter
|
|
||||||
count int
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
|
|
||||||
r.mutex.Lock()
|
|
||||||
defer r.mutex.Unlock()
|
|
||||||
r.proxyAdapter = proxyAdapter
|
|
||||||
r.count++
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) ClearProxyAdapter() {
|
|
||||||
r.mutex.Lock()
|
|
||||||
defer r.mutex.Unlock()
|
|
||||||
r.count--
|
|
||||||
if r.count == 0 {
|
|
||||||
r.proxyAdapter = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) Name() string {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.Name()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) Type() C.AdapterType {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.Type()
|
|
||||||
}
|
|
||||||
return C.AdapterType(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) Addr() string {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.Addr()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) SupportUDP() bool {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.SupportUDP()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.ProxyInfo()
|
|
||||||
}
|
|
||||||
return C.ProxyInfo{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.MarshalJSON()
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.StreamConnContext(ctx, c, metadata)
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.DialContext(ctx, metadata, opts...)
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) SupportUOT() bool {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.SupportUOT()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.SupportWithDialer()
|
|
||||||
}
|
|
||||||
return C.InvalidNet
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
|
|
||||||
}
|
|
||||||
return nil, C.ErrNotSupport
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.IsL3Protocol(metadata)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
|
|
||||||
if r.proxyAdapter != nil {
|
|
||||||
return r.proxyAdapter.Unwrap(metadata, touch)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ C.ProxyAdapter = (*refProxyAdapter)(nil)
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
//go:build with_gvisor
|
|
||||||
|
|
||||||
package outbound
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"runtime"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestWireGuardGC(t *testing.T) {
|
|
||||||
option := WireGuardOption{}
|
|
||||||
option.Server = "162.159.192.1"
|
|
||||||
option.Port = 2408
|
|
||||||
option.PrivateKey = "iOx7749AdqH3IqluG7+0YbGKd0m1mcEXAfGRzpy9rG8="
|
|
||||||
option.PublicKey = "bmXOC+F1FxEMF9dyiK2H5/1SUtzH0JuVo51h2wPfgyo="
|
|
||||||
option.Ip = "172.16.0.2"
|
|
||||||
option.Ipv6 = "2606:4700:110:8d29:be92:3a6a:f4:c437"
|
|
||||||
option.Reserved = []uint8{51, 69, 125}
|
|
||||||
wg, err := NewWireGuard(option)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
closeCh := make(chan struct{})
|
|
||||||
wg.closeCh = closeCh
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
err = wg.init(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// must do a small sleep before test GC
|
|
||||||
// because it maybe deadlocks if w.device.Close call too fast after w.device.Start
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
|
||||||
wg = nil
|
|
||||||
runtime.GC()
|
|
||||||
select {
|
|
||||||
case <-closeCh:
|
|
||||||
return
|
|
||||||
case <-ctx.Done():
|
|
||||||
t.Error("timeout not GC")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,8 +3,6 @@ package adapter
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/adapter/outbound"
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
"github.com/metacubex/mihomo/common/structure"
|
"github.com/metacubex/mihomo/common/structure"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
@ -18,12 +16,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
proxy C.ProxyAdapter
|
proxy outbound.ProxyAdapter
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
switch proxyType {
|
switch proxyType {
|
||||||
case "ss":
|
case "ss":
|
||||||
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
ssOption := &outbound.ShadowSocksOption{}
|
||||||
err = decoder.Decode(mapping, ssOption)
|
err = decoder.Decode(mapping, ssOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -56,7 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
Method: "GET",
|
Method: "GET",
|
||||||
Path: []string{"/"},
|
Path: []string{"/"},
|
||||||
},
|
},
|
||||||
ClientFingerprint: tlsC.GetGlobalFingerprint(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = decoder.Decode(mapping, vmessOption)
|
err = decoder.Decode(mapping, vmessOption)
|
||||||
|
@ -65,7 +62,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewVmess(*vmessOption)
|
proxy, err = outbound.NewVmess(*vmessOption)
|
||||||
case "vless":
|
case "vless":
|
||||||
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
vlessOption := &outbound.VlessOption{}
|
||||||
err = decoder.Decode(mapping, vlessOption)
|
err = decoder.Decode(mapping, vlessOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -79,7 +76,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
}
|
}
|
||||||
proxy, err = outbound.NewSnell(*snellOption)
|
proxy, err = outbound.NewSnell(*snellOption)
|
||||||
case "trojan":
|
case "trojan":
|
||||||
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
trojanOption := &outbound.TrojanOption{}
|
||||||
err = decoder.Decode(mapping, trojanOption)
|
err = decoder.Decode(mapping, trojanOption)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
|
@ -170,12 +167,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if muxOption.Enabled {
|
if muxOption.Enabled {
|
||||||
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
|
proxy, err = outbound.NewSingMux(*muxOption, proxy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy = outbound.NewAutoCloseProxyAdapter(proxy)
|
||||||
return NewProxy(proxy), nil
|
return NewProxy(proxy), nil
|
||||||
}
|
}
|
||||||
|
|
31
common/contextutils/afterfunc_compact.go
Normal file
31
common/contextutils/afterfunc_compact.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package contextutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func afterFunc(ctx context.Context, f func()) (stop func() bool) {
|
||||||
|
stopc := make(chan struct{})
|
||||||
|
once := sync.Once{} // either starts running f or stops f from running
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
once.Do(func() {
|
||||||
|
go f()
|
||||||
|
})
|
||||||
|
case <-stopc:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return func() bool {
|
||||||
|
stopped := false
|
||||||
|
once.Do(func() {
|
||||||
|
stopped = true
|
||||||
|
close(stopc)
|
||||||
|
})
|
||||||
|
return stopped
|
||||||
|
}
|
||||||
|
}
|
11
common/contextutils/afterfunc_go120.go
Normal file
11
common/contextutils/afterfunc_go120.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build !go1.21
|
||||||
|
|
||||||
|
package contextutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AfterFunc(ctx context.Context, f func()) (stop func() bool) {
|
||||||
|
return afterFunc(ctx, f)
|
||||||
|
}
|
9
common/contextutils/afterfunc_go121.go
Normal file
9
common/contextutils/afterfunc_go121.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
//go:build go1.21
|
||||||
|
|
||||||
|
package contextutils
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func AfterFunc(ctx context.Context, f func()) (stop func() bool) {
|
||||||
|
return context.AfterFunc(ctx, f)
|
||||||
|
}
|
100
common/contextutils/afterfunc_test.go
Normal file
100
common/contextutils/afterfunc_test.go
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
package contextutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
shortDuration = 1 * time.Millisecond // a reasonable duration to block in a test
|
||||||
|
veryLongDuration = 1000 * time.Hour // an arbitrary upper bound on the test's running time
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAfterFuncCalledAfterCancel(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
donec := make(chan struct{})
|
||||||
|
stop := afterFunc(ctx, func() {
|
||||||
|
close(donec)
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
t.Fatalf("AfterFunc called before context is done")
|
||||||
|
case <-time.After(shortDuration):
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
case <-time.After(veryLongDuration):
|
||||||
|
t.Fatalf("AfterFunc not called after context is canceled")
|
||||||
|
}
|
||||||
|
if stop() {
|
||||||
|
t.Fatalf("stop() = true, want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterFuncCalledAfterTimeout(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), shortDuration)
|
||||||
|
defer cancel()
|
||||||
|
donec := make(chan struct{})
|
||||||
|
afterFunc(ctx, func() {
|
||||||
|
close(donec)
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
case <-time.After(veryLongDuration):
|
||||||
|
t.Fatalf("AfterFunc not called after context is canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterFuncCalledImmediately(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
donec := make(chan struct{})
|
||||||
|
afterFunc(ctx, func() {
|
||||||
|
close(donec)
|
||||||
|
})
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
case <-time.After(veryLongDuration):
|
||||||
|
t.Fatalf("AfterFunc not called for already-canceled context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAfterFuncNotCalledAfterStop(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
donec := make(chan struct{})
|
||||||
|
stop := afterFunc(ctx, func() {
|
||||||
|
close(donec)
|
||||||
|
})
|
||||||
|
if !stop() {
|
||||||
|
t.Fatalf("stop() = false, want true")
|
||||||
|
}
|
||||||
|
cancel()
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
t.Fatalf("AfterFunc called for already-canceled context")
|
||||||
|
case <-time.After(shortDuration):
|
||||||
|
}
|
||||||
|
if stop() {
|
||||||
|
t.Fatalf("stop() = true, want false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This test verifies that canceling a context does not block waiting for AfterFuncs to finish.
|
||||||
|
func TestAfterFuncCalledAsynchronously(t *testing.T) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
donec := make(chan struct{})
|
||||||
|
stop := afterFunc(ctx, func() {
|
||||||
|
// The channel send blocks until donec is read from.
|
||||||
|
donec <- struct{}{}
|
||||||
|
})
|
||||||
|
defer stop()
|
||||||
|
cancel()
|
||||||
|
// After cancel returns, read from donec and unblock the AfterFunc.
|
||||||
|
select {
|
||||||
|
case <-donec:
|
||||||
|
case <-time.After(veryLongDuration):
|
||||||
|
t.Fatalf("AfterFunc not called after context is canceled")
|
||||||
|
}
|
||||||
|
}
|
|
@ -275,22 +275,24 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
vmess["skip-cert-verify"] = false
|
vmess["skip-cert-verify"] = false
|
||||||
|
|
||||||
vmess["cipher"] = "auto"
|
vmess["cipher"] = "auto"
|
||||||
if cipher, ok := values["scy"]; ok && cipher != "" {
|
if cipher, ok := values["scy"].(string); ok && cipher != "" {
|
||||||
vmess["cipher"] = cipher
|
vmess["cipher"] = cipher
|
||||||
}
|
}
|
||||||
|
|
||||||
if sni, ok := values["sni"]; ok && sni != "" {
|
if sni, ok := values["sni"].(string); ok && sni != "" {
|
||||||
vmess["servername"] = sni
|
vmess["servername"] = sni
|
||||||
}
|
}
|
||||||
|
|
||||||
network, _ := values["net"].(string)
|
network, ok := values["net"].(string)
|
||||||
network = strings.ToLower(network)
|
if ok {
|
||||||
if values["type"] == "http" {
|
network = strings.ToLower(network)
|
||||||
network = "http"
|
if values["type"] == "http" {
|
||||||
} else if network == "http" {
|
network = "http"
|
||||||
network = "h2"
|
} else if network == "http" {
|
||||||
|
network = "h2"
|
||||||
|
}
|
||||||
|
vmess["network"] = network
|
||||||
}
|
}
|
||||||
vmess["network"] = network
|
|
||||||
|
|
||||||
tls, ok := values["tls"].(string)
|
tls, ok := values["tls"].(string)
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -307,12 +309,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
case "http":
|
case "http":
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
httpOpts := make(map[string]any)
|
httpOpts := make(map[string]any)
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
if host, ok := values["host"].(string); ok && host != "" {
|
||||||
headers["Host"] = []string{host.(string)}
|
headers["Host"] = []string{host}
|
||||||
}
|
}
|
||||||
httpOpts["path"] = []string{"/"}
|
httpOpts["path"] = []string{"/"}
|
||||||
if path, ok := values["path"]; ok && path != "" {
|
if path, ok := values["path"].(string); ok && path != "" {
|
||||||
httpOpts["path"] = []string{path.(string)}
|
httpOpts["path"] = []string{path}
|
||||||
}
|
}
|
||||||
httpOpts["headers"] = headers
|
httpOpts["headers"] = headers
|
||||||
|
|
||||||
|
@ -321,8 +323,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
case "h2":
|
case "h2":
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
h2Opts := make(map[string]any)
|
h2Opts := make(map[string]any)
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
if host, ok := values["host"].(string); ok && host != "" {
|
||||||
headers["Host"] = []string{host.(string)}
|
headers["Host"] = []string{host}
|
||||||
}
|
}
|
||||||
|
|
||||||
h2Opts["path"] = values["path"]
|
h2Opts["path"] = values["path"]
|
||||||
|
@ -334,11 +336,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
||||||
headers := make(map[string]any)
|
headers := make(map[string]any)
|
||||||
wsOpts := make(map[string]any)
|
wsOpts := make(map[string]any)
|
||||||
wsOpts["path"] = "/"
|
wsOpts["path"] = "/"
|
||||||
if host, ok := values["host"]; ok && host != "" {
|
if host, ok := values["host"].(string); ok && host != "" {
|
||||||
headers["Host"] = host.(string)
|
headers["Host"] = host
|
||||||
}
|
}
|
||||||
if path, ok := values["path"]; ok && path != "" {
|
if path, ok := values["path"].(string); ok && path != "" {
|
||||||
path := path.(string)
|
path := path
|
||||||
pathURL, err := url.Parse(path)
|
pathURL, err := url.Parse(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
query := pathURL.Query()
|
query := pathURL.Query()
|
||||||
|
|
|
@ -3,29 +3,37 @@ package net
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/common/contextutils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine.
|
// SetupContextForConn is a helper function that starts connection I/O interrupter.
|
||||||
|
// if ctx be canceled before done called, it will close the connection.
|
||||||
|
// should use like this:
|
||||||
|
//
|
||||||
|
// func streamConn(ctx context.Context, conn net.Conn) (_ net.Conn, err error) {
|
||||||
|
// if ctx.Done() != nil {
|
||||||
|
// done := N.SetupContextForConn(ctx, conn)
|
||||||
|
// defer done(&err)
|
||||||
|
// }
|
||||||
|
// conn, err := xxx
|
||||||
|
// return conn, err
|
||||||
|
// }
|
||||||
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
|
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
|
||||||
var (
|
stopc := make(chan struct{})
|
||||||
quit = make(chan struct{})
|
stop := contextutils.AfterFunc(ctx, func() {
|
||||||
interrupt = make(chan error, 1)
|
// Close the connection, discarding the error
|
||||||
)
|
_ = conn.Close()
|
||||||
go func() {
|
close(stopc)
|
||||||
select {
|
})
|
||||||
case <-quit:
|
|
||||||
interrupt <- nil
|
|
||||||
case <-ctx.Done():
|
|
||||||
// Close the connection, discarding the error
|
|
||||||
_ = conn.Close()
|
|
||||||
interrupt <- ctx.Err()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return func(inputErr *error) {
|
return func(inputErr *error) {
|
||||||
close(quit)
|
if !stop() {
|
||||||
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
|
// The AfterFunc was started, wait for it to complete.
|
||||||
// Return context error to user.
|
<-stopc
|
||||||
inputErr = &ctxErr
|
if ctxErr := ctx.Err(); ctxErr != nil && inputErr != nil {
|
||||||
|
// Return context error to user.
|
||||||
|
*inputErr = ctxErr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
103
common/net/context_test.go
Normal file
103
common/net/context_test.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package net_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testRead(ctx context.Context, conn net.Conn) (err error) {
|
||||||
|
if ctx.Done() != nil {
|
||||||
|
done := N.SetupContextForConn(ctx, conn)
|
||||||
|
defer done(&err)
|
||||||
|
}
|
||||||
|
_, err = conn.Read(make([]byte, 1))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupContextForConnWithCancel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c1, c2 := N.Pipe()
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
errc := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errc <- testRead(ctx, c1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-errc:
|
||||||
|
t.Fatal("conn closed before cancel")
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errc:
|
||||||
|
assert.ErrorIs(t, err, context.Canceled)
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
t.Fatal("conn not be canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupContextForConnWithTimeout1(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c1, c2 := N.Pipe()
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
errc := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errc <- testRead(ctx, c1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errc:
|
||||||
|
if !errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
|
t.Fatal("conn closed before timeout")
|
||||||
|
}
|
||||||
|
assert.ErrorIs(t, err, context.DeadlineExceeded)
|
||||||
|
case <-time.After(200 * time.Millisecond):
|
||||||
|
t.Fatal("conn not be canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupContextForConnWithTimeout2(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
c1, c2 := N.Pipe()
|
||||||
|
defer c1.Close()
|
||||||
|
defer c2.Close()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
errc := make(chan error)
|
||||||
|
go func() {
|
||||||
|
errc <- testRead(ctx, c1)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-errc:
|
||||||
|
t.Fatal("conn closed before cancel")
|
||||||
|
case <-time.After(100 * time.Millisecond):
|
||||||
|
c2.Write(make([]byte, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errc:
|
||||||
|
assert.Nil(t, ctx.Err())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
case <-time.After(200 * time.Millisecond):
|
||||||
|
t.Fatal("conn not be canceled")
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,6 +77,6 @@ func (c *refConn) WriterReplaceable() bool { // Relay() will handle reference
|
||||||
|
|
||||||
var _ ExtendedConn = (*refConn)(nil)
|
var _ ExtendedConn = (*refConn)(nil)
|
||||||
|
|
||||||
func NewRefConn(conn net.Conn, ref any) net.Conn {
|
func NewRefConn(conn net.Conn, ref any) ExtendedConn {
|
||||||
return &refConn{conn: NewExtendedConn(conn), ref: ref}
|
return &refConn{conn: NewExtendedConn(conn), ref: ref}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ package net
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
@ -16,7 +18,11 @@ type Path interface {
|
||||||
|
|
||||||
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
||||||
if certificate == "" && privateKey == "" {
|
if certificate == "" && privateKey == "" {
|
||||||
return newRandomTLSKeyPair()
|
var err error
|
||||||
|
certificate, privateKey, _, err = NewRandomTLSKeyPair()
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
|
||||||
if painTextErr == nil {
|
if painTextErr == nil {
|
||||||
|
@ -32,10 +38,10 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro
|
||||||
return cert, nil
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRandomTLSKeyPair() (tls.Certificate, error) {
|
func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) {
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, err
|
return
|
||||||
}
|
}
|
||||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||||
certDER, err := x509.CreateCertificate(
|
certDER, err := x509.CreateCertificate(
|
||||||
|
@ -45,14 +51,15 @@ func newRandomTLSKeyPair() (tls.Certificate, error) {
|
||||||
&key.PublicKey,
|
&key.PublicKey,
|
||||||
key)
|
key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, err
|
return
|
||||||
}
|
}
|
||||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
cert, err := x509.ParseCertificate(certDER)
|
||||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
|
||||||
|
|
||||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tls.Certificate{}, err
|
return
|
||||||
}
|
}
|
||||||
return tlsCert, nil
|
hash := sha256.Sum256(cert.Raw)
|
||||||
|
fingerprint = hex.EncodeToString(hash[:])
|
||||||
|
privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
|
||||||
|
certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,73 +0,0 @@
|
||||||
package nnip
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"net"
|
|
||||||
"net/netip"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IpToAddr converts the net.IP to netip.Addr.
|
|
||||||
// If slice's length is not 4 or 16, IpToAddr returns netip.Addr{}
|
|
||||||
func IpToAddr(slice net.IP) netip.Addr {
|
|
||||||
ip := slice
|
|
||||||
if len(ip) != 4 {
|
|
||||||
if ip = slice.To4(); ip == nil {
|
|
||||||
ip = slice
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if addr, ok := netip.AddrFromSlice(ip); ok {
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
return netip.Addr{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnMasked returns p's last IP address.
|
|
||||||
// If p is invalid, UnMasked returns netip.Addr{}
|
|
||||||
func UnMasked(p netip.Prefix) netip.Addr {
|
|
||||||
if !p.IsValid() {
|
|
||||||
return netip.Addr{}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := p.Addr().As16()
|
|
||||||
|
|
||||||
hi := binary.BigEndian.Uint64(buf[:8])
|
|
||||||
lo := binary.BigEndian.Uint64(buf[8:])
|
|
||||||
|
|
||||||
bits := p.Bits()
|
|
||||||
if bits <= 32 {
|
|
||||||
bits += 96
|
|
||||||
}
|
|
||||||
|
|
||||||
hi = hi | ^uint64(0)>>bits
|
|
||||||
lo = lo | ^(^uint64(0) << (128 - bits))
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(buf[:8], hi)
|
|
||||||
binary.BigEndian.PutUint64(buf[8:], lo)
|
|
||||||
|
|
||||||
addr := netip.AddrFrom16(buf)
|
|
||||||
if p.Addr().Is4() {
|
|
||||||
return addr.Unmap()
|
|
||||||
}
|
|
||||||
return addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrefixCompare returns an integer comparing two prefixes.
|
|
||||||
// The result will be 0 if p == p2, -1 if p < p2, and +1 if p > p2.
|
|
||||||
// modify from https://github.com/golang/go/issues/61642#issuecomment-1848587909
|
|
||||||
func PrefixCompare(p, p2 netip.Prefix) int {
|
|
||||||
// compare by validity, address family and prefix base address
|
|
||||||
if c := p.Masked().Addr().Compare(p2.Masked().Addr()); c != 0 {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
// compare by prefix length
|
|
||||||
f1, f2 := p.Bits(), p2.Bits()
|
|
||||||
if f1 < f2 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
if f1 > f2 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
// compare by prefix address
|
|
||||||
return p.Addr().Compare(p2.Addr())
|
|
||||||
}
|
|
|
@ -10,6 +10,7 @@ type Observable[T any] struct {
|
||||||
listener map[Subscription[T]]*Subscriber[T]
|
listener map[Subscription[T]]*Subscriber[T]
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
done bool
|
done bool
|
||||||
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Observable[T]) process() {
|
func (o *Observable[T]) process() {
|
||||||
|
@ -31,6 +32,7 @@ func (o *Observable[T]) close() {
|
||||||
for _, sub := range o.listener {
|
for _, sub := range o.listener {
|
||||||
sub.Close()
|
sub.Close()
|
||||||
}
|
}
|
||||||
|
close(o.stopCh)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Observable[T]) Subscribe() (Subscription[T], error) {
|
func (o *Observable[T]) Subscribe() (Subscription[T], error) {
|
||||||
|
@ -59,6 +61,7 @@ func NewObservable[T any](iter Iterable[T]) *Observable[T] {
|
||||||
observable := &Observable[T]{
|
observable := &Observable[T]{
|
||||||
iterable: iter,
|
iterable: iter,
|
||||||
listener: map[Subscription[T]]*Subscriber[T]{},
|
listener: map[Subscription[T]]*Subscriber[T]{},
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
}
|
}
|
||||||
go observable.process()
|
go observable.process()
|
||||||
return observable
|
return observable
|
||||||
|
|
|
@ -70,9 +70,11 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
|
||||||
src := NewObservable[int](iter)
|
src := NewObservable[int](iter)
|
||||||
data, _ := src.Subscribe()
|
data, _ := src.Subscribe()
|
||||||
<-data
|
<-data
|
||||||
|
select {
|
||||||
_, closed := src.Subscribe()
|
case <-src.stopCh:
|
||||||
assert.NotNil(t, closed)
|
case <-time.After(time.Second):
|
||||||
|
assert.Fail(t, "timeout not stop")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
|
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {
|
||||||
|
|
|
@ -22,9 +22,10 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPicker_Basic(t *testing.T) {
|
func TestPicker_Basic(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
picker, ctx := WithContext[int](context.Background())
|
picker, ctx := WithContext[int](context.Background())
|
||||||
picker.Go(sleepAndSend(ctx, 30, 2))
|
picker.Go(sleepAndSend(ctx, 200, 2))
|
||||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
picker.Go(sleepAndSend(ctx, 100, 1))
|
||||||
|
|
||||||
number := picker.Wait()
|
number := picker.Wait()
|
||||||
assert.NotNil(t, number)
|
assert.NotNil(t, number)
|
||||||
|
@ -32,8 +33,9 @@ func TestPicker_Basic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPicker_Timeout(t *testing.T) {
|
func TestPicker_Timeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
|
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
|
||||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
picker.Go(sleepAndSend(ctx, 100, 1))
|
||||||
|
|
||||||
number := picker.Wait()
|
number := picker.Wait()
|
||||||
assert.Equal(t, number, lo.Empty[int]())
|
assert.Equal(t, number, lo.Empty[int]())
|
||||||
|
|
|
@ -13,25 +13,26 @@ type call[T any] struct {
|
||||||
|
|
||||||
type Single[T any] struct {
|
type Single[T any] struct {
|
||||||
mux sync.Mutex
|
mux sync.Mutex
|
||||||
last time.Time
|
|
||||||
wait time.Duration
|
wait time.Duration
|
||||||
call *call[T]
|
call *call[T]
|
||||||
result *Result[T]
|
result *Result[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result[T any] struct {
|
type Result[T any] struct {
|
||||||
Val T
|
Val T
|
||||||
Err error
|
Err error
|
||||||
|
Time time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do single.Do likes sync.singleFlight
|
// Do single.Do likes sync.singleFlight
|
||||||
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
now := time.Now()
|
result := s.result
|
||||||
if now.Before(s.last.Add(s.wait)) {
|
if result != nil && time.Since(result.Time) < s.wait {
|
||||||
s.mux.Unlock()
|
s.mux.Unlock()
|
||||||
return s.result.Val, s.result.Err, true
|
return result.Val, result.Err, true
|
||||||
}
|
}
|
||||||
|
s.result = nil // The result has expired, clear it
|
||||||
|
|
||||||
if callM := s.call; callM != nil {
|
if callM := s.call; callM != nil {
|
||||||
s.mux.Unlock()
|
s.mux.Unlock()
|
||||||
|
@ -47,15 +48,19 @@ func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
||||||
callM.wg.Done()
|
callM.wg.Done()
|
||||||
|
|
||||||
s.mux.Lock()
|
s.mux.Lock()
|
||||||
s.call = nil
|
if s.call == callM { // maybe reset when fn is running
|
||||||
s.result = &Result[T]{callM.val, callM.err}
|
s.call = nil
|
||||||
s.last = now
|
s.result = &Result[T]{callM.val, callM.err, time.Now()}
|
||||||
|
}
|
||||||
s.mux.Unlock()
|
s.mux.Unlock()
|
||||||
return callM.val, callM.err, false
|
return callM.val, callM.err, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Single[T]) Reset() {
|
func (s *Single[T]) Reset() {
|
||||||
s.last = time.Time{}
|
s.mux.Lock()
|
||||||
|
s.call = nil
|
||||||
|
s.result = nil
|
||||||
|
s.mux.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSingle[T any](wait time.Duration) *Single[T] {
|
func NewSingle[T any](wait time.Duration) *Single[T] {
|
||||||
|
|
|
@ -11,12 +11,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
single := NewSingle[int](time.Millisecond * 30)
|
t.Parallel()
|
||||||
|
single := NewSingle[int](time.Millisecond * 200)
|
||||||
foo := 0
|
foo := 0
|
||||||
shardCount := atomic.NewInt32(0)
|
shardCount := atomic.NewInt32(0)
|
||||||
call := func() (int, error) {
|
call := func() (int, error) {
|
||||||
foo++
|
foo++
|
||||||
time.Sleep(time.Millisecond * 5)
|
time.Sleep(time.Millisecond * 20)
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +40,8 @@ func TestBasic(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTimer(t *testing.T) {
|
func TestTimer(t *testing.T) {
|
||||||
single := NewSingle[int](time.Millisecond * 30)
|
t.Parallel()
|
||||||
|
single := NewSingle[int](time.Millisecond * 200)
|
||||||
foo := 0
|
foo := 0
|
||||||
callM := func() (int, error) {
|
callM := func() (int, error) {
|
||||||
foo++
|
foo++
|
||||||
|
@ -47,7 +49,7 @@ func TestTimer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, _ = single.Do(callM)
|
_, _, _ = single.Do(callM)
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
_, _, shard := single.Do(callM)
|
_, _, shard := single.Do(callM)
|
||||||
|
|
||||||
assert.Equal(t, 1, foo)
|
assert.Equal(t, 1, foo)
|
||||||
|
@ -55,7 +57,8 @@ func TestTimer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReset(t *testing.T) {
|
func TestReset(t *testing.T) {
|
||||||
single := NewSingle[int](time.Millisecond * 30)
|
t.Parallel()
|
||||||
|
single := NewSingle[int](time.Millisecond * 200)
|
||||||
foo := 0
|
foo := 0
|
||||||
callM := func() (int, error) {
|
callM := func() (int, error) {
|
||||||
foo++
|
foo++
|
||||||
|
|
|
@ -141,7 +141,6 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
|
||||||
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
||||||
tlsConfig.InsecureSkipVerify = true
|
tlsConfig.InsecureSkipVerify = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/component/iface"
|
"github.com/metacubex/mihomo/component/iface"
|
||||||
|
|
||||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||||
|
@ -86,12 +85,14 @@ func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
dnsAddr := make([]netip.Addr, l)
|
results := make([]netip.Addr, 0, len(dns))
|
||||||
for i := 0; i < l; i++ {
|
for _, ip := range dns {
|
||||||
dnsAddr[i] = nnip.IpToAddr(dns[i])
|
if addr, ok := netip.AddrFromSlice(ip); ok {
|
||||||
|
results = append(results, addr.Unmap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result <- dnsAddr
|
result <- results
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/component/keepalive"
|
"github.com/metacubex/mihomo/component/keepalive"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
"github.com/metacubex/mihomo/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,34 +20,16 @@ const (
|
||||||
DefaultUDPTimeout = DefaultTCPTimeout
|
DefaultUDPTimeout = DefaultTCPTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error)
|
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dialMux sync.Mutex
|
dialMux sync.Mutex
|
||||||
IP4PEnable bool
|
|
||||||
actualSingleStackDialContext = serialSingleStackDialContext
|
actualSingleStackDialContext = serialSingleStackDialContext
|
||||||
actualDualStackDialContext = serialDualStackDialContext
|
actualDualStackDialContext = serialDualStackDialContext
|
||||||
tcpConcurrent = false
|
tcpConcurrent = false
|
||||||
fallbackTimeout = 300 * time.Millisecond
|
fallbackTimeout = 300 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
func applyOptions(options ...Option) *option {
|
|
||||||
opt := &option{
|
|
||||||
interfaceName: DefaultInterface.Load(),
|
|
||||||
routingMark: int(DefaultRoutingMark.Load()),
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range DefaultOptions {
|
|
||||||
o(opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, o := range options {
|
|
||||||
o(opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return opt
|
|
||||||
}
|
|
||||||
|
|
||||||
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
|
||||||
opt := applyOptions(options...)
|
opt := applyOptions(options...)
|
||||||
|
|
||||||
|
@ -79,38 +59,43 @@ func DialContext(ctx context.Context, network, address string, options ...Option
|
||||||
}
|
}
|
||||||
|
|
||||||
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
|
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
|
||||||
cfg := applyOptions(options...)
|
opt := applyOptions(options...)
|
||||||
|
|
||||||
lc := &net.ListenConfig{}
|
lc := &net.ListenConfig{}
|
||||||
if cfg.addrReuse {
|
if opt.addrReuse {
|
||||||
addrReuseToListenConfig(lc)
|
addrReuseToListenConfig(lc)
|
||||||
}
|
}
|
||||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
|
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
|
||||||
socketHookToListenConfig(lc)
|
socketHookToListenConfig(lc)
|
||||||
} else {
|
} else {
|
||||||
interfaceName := cfg.interfaceName
|
if opt.interfaceName == "" {
|
||||||
if interfaceName == "" {
|
opt.interfaceName = DefaultInterface.Load()
|
||||||
|
}
|
||||||
|
if opt.interfaceName == "" {
|
||||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||||
interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||||
// avoid "The requested address is not valid in its context."
|
// avoid "The requested address is not valid in its context."
|
||||||
interfaceName = ""
|
opt.interfaceName = ""
|
||||||
}
|
}
|
||||||
if interfaceName != "" {
|
if opt.interfaceName != "" {
|
||||||
bind := bindIfaceToListenConfig
|
bind := bindIfaceToListenConfig
|
||||||
if cfg.fallbackBind {
|
if opt.fallbackBind {
|
||||||
bind = fallbackBindIfaceToListenConfig
|
bind = fallbackBindIfaceToListenConfig
|
||||||
}
|
}
|
||||||
addr, err := bind(interfaceName, lc, network, address, rAddrPort)
|
addr, err := bind(opt.interfaceName, lc, network, address, rAddrPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
address = addr
|
address = addr
|
||||||
}
|
}
|
||||||
if cfg.routingMark != 0 {
|
if opt.routingMark == 0 {
|
||||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
opt.routingMark = int(DefaultRoutingMark.Load())
|
||||||
|
}
|
||||||
|
if opt.routingMark != 0 {
|
||||||
|
bindMarkToListenConfig(opt.routingMark, lc, network, address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,11 +121,9 @@ func GetTcpConcurrent() bool {
|
||||||
return tcpConcurrent
|
return tcpConcurrent
|
||||||
}
|
}
|
||||||
|
|
||||||
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
var address string
|
var address string
|
||||||
if IP4PEnable {
|
destination, port = resolver.LookupIP4P(destination, port)
|
||||||
destination, port = lookupIP4P(destination, port)
|
|
||||||
}
|
|
||||||
address = net.JoinHostPort(destination.String(), port)
|
address = net.JoinHostPort(destination.String(), port)
|
||||||
|
|
||||||
netDialer := opt.netDialer
|
netDialer := opt.netDialer
|
||||||
|
@ -163,21 +146,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
|
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
|
||||||
socketHookToToDialer(dialer)
|
socketHookToToDialer(dialer)
|
||||||
} else {
|
} else {
|
||||||
interfaceName := opt.interfaceName // don't change the "opt", it's a pointer
|
if opt.interfaceName == "" {
|
||||||
if interfaceName == "" {
|
opt.interfaceName = DefaultInterface.Load()
|
||||||
|
}
|
||||||
|
if opt.interfaceName == "" {
|
||||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||||
interfaceName = finder.FindInterfaceName(destination)
|
opt.interfaceName = finder.FindInterfaceName(destination)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if interfaceName != "" {
|
if opt.interfaceName != "" {
|
||||||
bind := bindIfaceToDialer
|
bind := bindIfaceToDialer
|
||||||
if opt.fallbackBind {
|
if opt.fallbackBind {
|
||||||
bind = fallbackBindIfaceToDialer
|
bind = fallbackBindIfaceToDialer
|
||||||
}
|
}
|
||||||
if err := bind(interfaceName, dialer, network, destination); err != nil {
|
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if opt.routingMark == 0 {
|
||||||
|
opt.routingMark = int(DefaultRoutingMark.Load())
|
||||||
|
}
|
||||||
if opt.routingMark != 0 {
|
if opt.routingMark != 0 {
|
||||||
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
bindMarkToDialer(opt.routingMark, dialer, network, destination)
|
||||||
}
|
}
|
||||||
|
@ -189,26 +177,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
|
||||||
return dialer.DialContext(ctx, network, address)
|
return dialer.DialContext(ctx, network, address)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
return serialDialContext(ctx, network, ips, port, opt)
|
return serialDialContext(ctx, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
|
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
return parallelDialContext(ctx, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
if opt.prefer != 4 && opt.prefer != 6 {
|
if opt.prefer != 4 && opt.prefer != 6 {
|
||||||
return parallelDialContext(ctx, network, ips, port, opt)
|
return parallelDialContext(ctx, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
|
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
ipv4s, ipv6s := resolver.SortationAddr(ips)
|
||||||
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
||||||
return nil, ErrorNoIpAddress
|
return nil, ErrorNoIpAddress
|
||||||
|
@ -289,7 +277,7 @@ loop:
|
||||||
return nil, errors.Join(errs...)
|
return nil, errors.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
return nil, ErrorNoIpAddress
|
return nil, ErrorNoIpAddress
|
||||||
}
|
}
|
||||||
|
@ -328,7 +316,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
|
||||||
return nil, os.ErrDeadlineExceeded
|
return nil, os.ErrDeadlineExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) {
|
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
|
||||||
if len(ips) == 0 {
|
if len(ips) == 0 {
|
||||||
return nil, ErrorNoIpAddress
|
return nil, ErrorNoIpAddress
|
||||||
}
|
}
|
||||||
|
@ -394,23 +382,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
|
||||||
|
|
||||||
func NewDialer(options ...Option) Dialer {
|
func NewDialer(options ...Option) Dialer {
|
||||||
opt := applyOptions(options...)
|
opt := applyOptions(options...)
|
||||||
return Dialer{Opt: *opt}
|
return Dialer{Opt: opt}
|
||||||
}
|
|
||||||
|
|
||||||
func GetIP4PEnable(enableIP4PConvert bool) {
|
|
||||||
IP4PEnable = enableIP4PConvert
|
|
||||||
}
|
|
||||||
|
|
||||||
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
|
|
||||||
|
|
||||||
func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
|
|
||||||
ip := addr.AsSlice()
|
|
||||||
if ip[0] == 0x20 && ip[1] == 0x01 &&
|
|
||||||
ip[2] == 0x00 && ip[3] == 0x00 {
|
|
||||||
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
|
|
||||||
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
|
|
||||||
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
|
|
||||||
return addr, port
|
|
||||||
}
|
|
||||||
return addr, port
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
DefaultOptions []Option
|
|
||||||
DefaultInterface = atomic.NewTypedValue[string]("")
|
DefaultInterface = atomic.NewTypedValue[string]("")
|
||||||
DefaultRoutingMark = atomic.NewInt32(0)
|
DefaultRoutingMark = atomic.NewInt32(0)
|
||||||
|
|
||||||
|
@ -115,3 +114,15 @@ func WithOption(o option) Option {
|
||||||
*opt = o
|
*opt = o
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsZeroOptions(opts []Option) bool {
|
||||||
|
return applyOptions(opts...) == option{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyOptions(options ...Option) option {
|
||||||
|
opt := option{}
|
||||||
|
for _, o := range options {
|
||||||
|
o(&opt)
|
||||||
|
}
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -183,7 +184,7 @@ func New(options Options) (*Pool, error) {
|
||||||
hostAddr = options.IPNet.Masked().Addr()
|
hostAddr = options.IPNet.Masked().Addr()
|
||||||
gateway = hostAddr.Next()
|
gateway = hostAddr.Next()
|
||||||
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
first = gateway.Next().Next().Next() // default start with 198.18.0.4
|
||||||
last = nnip.UnMasked(options.IPNet)
|
last = netipx.PrefixLastIP(options.IPNet)
|
||||||
)
|
)
|
||||||
|
|
||||||
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
|
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {
|
||||||
|
|
|
@ -69,7 +69,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
if conn, err := inner.HandleTcp(address, specialProxy); err == nil {
|
if conn, err := inner.HandleTcp(inner.GetTunnel(), address, specialProxy); err == nil {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
} else {
|
} else {
|
||||||
return dialer.DialContext(ctx, network, address)
|
return dialer.DialContext(ctx, network, address)
|
||||||
|
|
|
@ -59,7 +59,7 @@ func SetNetListenConfig(lc *net.ListenConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TCPKeepAlive(c net.Conn) {
|
func TCPKeepAlive(c net.Conn) {
|
||||||
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil {
|
if tcp, ok := c.(TCPConn); ok && tcp != nil {
|
||||||
tcpKeepAlive(tcp)
|
tcpKeepAlive(tcp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,18 @@
|
||||||
|
|
||||||
package keepalive
|
package keepalive
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
type TCPConn interface {
|
||||||
|
net.Conn
|
||||||
|
SetKeepAlive(keepalive bool) error
|
||||||
|
SetKeepAlivePeriod(d time.Duration) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func tcpKeepAlive(tcp TCPConn) {
|
||||||
if DisableKeepAlive() {
|
if DisableKeepAlive() {
|
||||||
_ = tcp.SetKeepAlive(false)
|
_ = tcp.SetKeepAlive(false)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4,6 +4,12 @@ package keepalive
|
||||||
|
|
||||||
import "net"
|
import "net"
|
||||||
|
|
||||||
|
type TCPConn interface {
|
||||||
|
net.Conn
|
||||||
|
SetKeepAlive(keepalive bool) error
|
||||||
|
SetKeepAliveConfig(config net.KeepAliveConfig) error
|
||||||
|
}
|
||||||
|
|
||||||
func keepAliveConfig() net.KeepAliveConfig {
|
func keepAliveConfig() net.KeepAliveConfig {
|
||||||
config := net.KeepAliveConfig{
|
config := net.KeepAliveConfig{
|
||||||
Enable: true,
|
Enable: true,
|
||||||
|
@ -18,7 +24,7 @@ func keepAliveConfig() net.KeepAliveConfig {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
func tcpKeepAlive(tcp TCPConn) {
|
||||||
if DisableKeepAlive() {
|
if DisableKeepAlive() {
|
||||||
_ = tcp.SetKeepAlive(false)
|
_ = tcp.SetKeepAlive(false)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -87,6 +87,7 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
srcIP = srcIP.Unmap()
|
||||||
|
|
||||||
if ip == srcIP {
|
if ip == srcIP {
|
||||||
// xsocket_n.so_last_pid
|
// xsocket_n.so_last_pid
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -136,13 +135,14 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
|
||||||
switch {
|
switch {
|
||||||
case flag&0x1 > 0 && isIPv4:
|
case flag&0x1 > 0 && isIPv4:
|
||||||
// ipv4
|
// ipv4
|
||||||
srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4])
|
srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip : inp+s.ip+4])
|
||||||
case flag&0x2 > 0 && !isIPv4:
|
case flag&0x2 > 0 && !isIPv4:
|
||||||
// ipv6
|
// ipv6
|
||||||
srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4])
|
srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip-12 : inp+s.ip+4])
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
srcIP = srcIP.Unmap()
|
||||||
|
|
||||||
if ip != srcIP {
|
if ip != srcIP {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
"golang.org/x/sys/windows"
|
||||||
|
@ -137,7 +136,8 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize])
|
srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
|
||||||
|
srcIP = srcIP.Unmap()
|
||||||
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
|
||||||
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||||
continue
|
continue
|
||||||
|
|
37
component/resolver/ip4p.go
Normal file
37
component/resolver/ip4p.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ip4PEnable bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetIP4PEnable() bool {
|
||||||
|
return ip4PEnable
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetIP4PEnable(enableIP4PConvert bool) {
|
||||||
|
ip4PEnable = enableIP4PConvert
|
||||||
|
}
|
||||||
|
|
||||||
|
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
|
||||||
|
|
||||||
|
func LookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
|
||||||
|
if ip4PEnable {
|
||||||
|
ip := addr.AsSlice()
|
||||||
|
if ip[0] == 0x20 && ip[1] == 0x01 &&
|
||||||
|
ip[2] == 0x00 && ip[3] == 0x00 {
|
||||||
|
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
|
||||||
|
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
|
||||||
|
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
|
||||||
|
return addr, port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addr, port
|
||||||
|
}
|
|
@ -196,6 +196,26 @@ func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
|
||||||
return ResolveIPWithResolver(ctx, host, DefaultResolver)
|
return ResolveIPWithResolver(ctx, host, DefaultResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResolveIPPrefer6WithResolver same as ResolveIP, but with a resolver
|
||||||
|
func ResolveIPPrefer6WithResolver(ctx context.Context, host string, r Resolver) (netip.Addr, error) {
|
||||||
|
ips, err := LookupIPWithResolver(ctx, host, r)
|
||||||
|
if err != nil {
|
||||||
|
return netip.Addr{}, err
|
||||||
|
} else if len(ips) == 0 {
|
||||||
|
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
|
||||||
|
}
|
||||||
|
ipv4s, ipv6s := SortationAddr(ips)
|
||||||
|
if len(ipv6s) > 0 {
|
||||||
|
return ipv6s[randv2.IntN(len(ipv6s))], nil
|
||||||
|
}
|
||||||
|
return ipv4s[randv2.IntN(len(ipv4s))], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveIPPrefer6 with a host, return ip and priority return TypeAAAA
|
||||||
|
func ResolveIPPrefer6(ctx context.Context, host string) (netip.Addr, error) {
|
||||||
|
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
|
||||||
|
}
|
||||||
|
|
||||||
func ResetConnection() {
|
func ResetConnection() {
|
||||||
if DefaultResolver != nil {
|
if DefaultResolver != nil {
|
||||||
go DefaultResolver.ResetConnection()
|
go DefaultResolver.ResetConnection()
|
||||||
|
|
|
@ -399,9 +399,7 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = q.tryAssemble()
|
return q.tryAssemble()
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *quicPacketSender) tryAssemble() error {
|
func (q *quicPacketSender) tryAssemble() error {
|
||||||
|
@ -415,7 +413,17 @@ func (q *quicPacketSender) tryAssemble() error {
|
||||||
|
|
||||||
if len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(len(q.buffer)) {
|
if len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(len(q.buffer)) {
|
||||||
q.lock.RUnlock()
|
q.lock.RUnlock()
|
||||||
return ErrNoClue
|
// incomplete fragment, just return
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(q.buffer) <= 4 ||
|
||||||
|
// Handshake Type (1) + uint24 Length (3) + ClientHello body
|
||||||
|
// maxCryptoStreamOffset is in the valid range of uint16 so just ignore the q.buffer[1]
|
||||||
|
int(binary.BigEndian.Uint16([]byte{q.buffer[2], q.buffer[3]})+4) != len(q.buffer) {
|
||||||
|
q.lock.RUnlock()
|
||||||
|
// end of segment not reached, just return
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := ReadClientHello(q.buffer)
|
domain, err := ReadClientHello(q.buffer)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import (
|
||||||
utls "github.com/metacubex/utls"
|
utls "github.com/metacubex/utls"
|
||||||
"golang.org/x/crypto/chacha20poly1305"
|
"golang.org/x/crypto/chacha20poly1305"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -36,9 +37,9 @@ type RealityConfig struct {
|
||||||
ShortID [RealityMaxShortIDLen]byte
|
ShortID [RealityMaxShortIDLen]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
|
||||||
retry := 0
|
retry := 0
|
||||||
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
|
for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
|
||||||
verifier := &realityVerifier{
|
verifier := &realityVerifier{
|
||||||
serverName: tlsConfig.ServerName,
|
serverName: tlsConfig.ServerName,
|
||||||
}
|
}
|
||||||
|
@ -60,6 +61,27 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------for X25519MLKEM768 does not work properly with reality-------
|
||||||
|
// Iterate over extensions and check
|
||||||
|
for _, extension := range uConn.Extensions {
|
||||||
|
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
|
||||||
|
ce.Curves = slices.DeleteFunc(ce.Curves, func(curveID utls.CurveID) bool {
|
||||||
|
return curveID == utls.X25519MLKEM768
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if ks, ok := extension.(*utls.KeyShareExtension); ok {
|
||||||
|
ks.KeyShares = slices.DeleteFunc(ks.KeyShares, func(share utls.KeyShare) bool {
|
||||||
|
return share.Group == utls.X25519MLKEM768
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Rebuild the client hello
|
||||||
|
err = uConn.BuildHandshakeState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
hello := uConn.HandshakeState.Hello
|
hello := uConn.HandshakeState.Hello
|
||||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||||
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
|
||||||
|
|
|
@ -279,6 +279,10 @@ type RawTun struct {
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||||
|
ExcludeSrcPort []uint16 `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
|
||||||
|
ExcludeSrcPortRange []string `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
|
||||||
|
ExcludeDstPort []uint16 `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
|
||||||
|
ExcludeDstPortRange []string `yaml:"exclude-dst-port-range" json:"exclude-dst-port-range,omitempty"`
|
||||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||||
|
@ -1560,6 +1564,10 @@ func parseTun(rawTun RawTun, general *General) error {
|
||||||
IncludeUIDRange: rawTun.IncludeUIDRange,
|
IncludeUIDRange: rawTun.IncludeUIDRange,
|
||||||
ExcludeUID: rawTun.ExcludeUID,
|
ExcludeUID: rawTun.ExcludeUID,
|
||||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||||
|
ExcludeSrcPort: rawTun.ExcludeSrcPort,
|
||||||
|
ExcludeSrcPortRange: rawTun.ExcludeSrcPortRange,
|
||||||
|
ExcludeDstPort: rawTun.ExcludeDstPort,
|
||||||
|
ExcludeDstPortRange: rawTun.ExcludeDstPortRange,
|
||||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||||
IncludePackage: rawTun.IncludePackage,
|
IncludePackage: rawTun.IncludePackage,
|
||||||
ExcludePackage: rawTun.ExcludePackage,
|
ExcludePackage: rawTun.ExcludePackage,
|
||||||
|
|
|
@ -149,6 +149,9 @@ type ProxyAdapter interface {
|
||||||
|
|
||||||
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
|
||||||
Unwrap(metadata *Metadata, touch bool) Proxy
|
Unwrap(metadata *Metadata, touch bool) Proxy
|
||||||
|
|
||||||
|
// Close releasing associated resources
|
||||||
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group interface {
|
type Group interface {
|
||||||
|
|
|
@ -6,11 +6,15 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/transport/socks5"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Socks addr type
|
// SOCKS address types as defined in RFC 1928 section 5.
|
||||||
|
const (
|
||||||
|
AtypIPv4 AddrType = 1
|
||||||
|
AtypDomainName AddrType = 3
|
||||||
|
AtypIPv6 AddrType = 4
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TCP NetWork = iota
|
TCP NetWork = iota
|
||||||
UDP
|
UDP
|
||||||
|
@ -37,6 +41,21 @@ const (
|
||||||
INNER
|
INNER
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type AddrType byte
|
||||||
|
|
||||||
|
func (a AddrType) String() string {
|
||||||
|
switch a {
|
||||||
|
case AtypIPv4:
|
||||||
|
return "IPv4"
|
||||||
|
case AtypDomainName:
|
||||||
|
return "DomainName"
|
||||||
|
case AtypIPv6:
|
||||||
|
return "IPv6"
|
||||||
|
default:
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type NetWork int
|
type NetWork int
|
||||||
|
|
||||||
func (n NetWork) String() string {
|
func (n NetWork) String() string {
|
||||||
|
@ -207,14 +226,14 @@ func (m *Metadata) SourceValid() bool {
|
||||||
return m.SrcPort != 0 && m.SrcIP.IsValid()
|
return m.SrcPort != 0 && m.SrcIP.IsValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metadata) AddrType() int {
|
func (m *Metadata) AddrType() AddrType {
|
||||||
switch true {
|
switch true {
|
||||||
case m.Host != "" || !m.DstIP.IsValid():
|
case m.Host != "" || !m.DstIP.IsValid():
|
||||||
return socks5.AtypDomainName
|
return AtypDomainName
|
||||||
case m.DstIP.Is4():
|
case m.DstIP.Is4():
|
||||||
return socks5.AtypIPv4
|
return AtypIPv4
|
||||||
default:
|
default:
|
||||||
return socks5.AtypIPv6
|
return AtypIPv6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/lru"
|
"github.com/metacubex/mihomo/common/lru"
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/component/fakeip"
|
"github.com/metacubex/mihomo/component/fakeip"
|
||||||
R "github.com/metacubex/mihomo/component/resolver"
|
R "github.com/metacubex/mihomo/component/resolver"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
@ -120,14 +119,21 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware {
|
||||||
|
|
||||||
switch a := ans.(type) {
|
switch a := ans.(type) {
|
||||||
case *D.A:
|
case *D.A:
|
||||||
ip = nnip.IpToAddr(a.A)
|
ip, _ = netip.AddrFromSlice(a.A)
|
||||||
ttl = a.Hdr.Ttl
|
ttl = a.Hdr.Ttl
|
||||||
case *D.AAAA:
|
case *D.AAAA:
|
||||||
ip = nnip.IpToAddr(a.AAAA)
|
ip, _ = netip.AddrFromSlice(a.AAAA)
|
||||||
ttl = a.Hdr.Ttl
|
ttl = a.Hdr.Ttl
|
||||||
default:
|
default:
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if !ip.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !ip.IsGlobalUnicast() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip = ip.Unmap()
|
||||||
|
|
||||||
if ttl < 1 {
|
if ttl < 1 {
|
||||||
ttl = 1
|
ttl = 1
|
||||||
|
|
|
@ -19,6 +19,16 @@ func dnsReadConfig() (servers []string, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, aa := range aas {
|
for _, aa := range aas {
|
||||||
|
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
|
||||||
|
if aa.OperStatus != windows.IfOperStatusUp {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only take interfaces which have at least one gateway
|
||||||
|
if aa.FirstGatewayAddress == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
|
||||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
sa, err := dns.Address.Sockaddr.Sockaddr()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -63,7 +73,8 @@ func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
|
||||||
l := uint32(15000) // recommended initial size
|
l := uint32(15000) // recommended initial size
|
||||||
for {
|
for {
|
||||||
b = make([]byte, l)
|
b = make([]byte, l)
|
||||||
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
|
const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS
|
||||||
|
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if l == 0 {
|
if l == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
|
20
dns/util.go
20
dns/util.go
|
@ -10,7 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
"github.com/metacubex/mihomo/common/picker"
|
"github.com/metacubex/mihomo/common/picker"
|
||||||
"github.com/metacubex/mihomo/component/dialer"
|
"github.com/metacubex/mihomo/component/dialer"
|
||||||
"github.com/metacubex/mihomo/component/resolver"
|
"github.com/metacubex/mihomo/component/resolver"
|
||||||
|
@ -150,19 +149,24 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
func msgToIP(msg *D.Msg) []netip.Addr {
|
func msgToIP(msg *D.Msg) (ips []netip.Addr) {
|
||||||
ips := []netip.Addr{}
|
|
||||||
|
|
||||||
for _, answer := range msg.Answer {
|
for _, answer := range msg.Answer {
|
||||||
|
var ip netip.Addr
|
||||||
switch ans := answer.(type) {
|
switch ans := answer.(type) {
|
||||||
case *D.AAAA:
|
case *D.AAAA:
|
||||||
ips = append(ips, nnip.IpToAddr(ans.AAAA))
|
ip, _ = netip.AddrFromSlice(ans.AAAA)
|
||||||
case *D.A:
|
case *D.A:
|
||||||
ips = append(ips, nnip.IpToAddr(ans.A))
|
ip, _ = netip.AddrFromSlice(ans.A)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if !ip.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ip = ip.Unmap()
|
||||||
|
ips = append(ips, ip)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
return ips
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func msgToDomain(msg *D.Msg) string {
|
func msgToDomain(msg *D.Msg) string {
|
||||||
|
|
|
@ -1298,6 +1298,31 @@ listeners:
|
||||||
# password: "example"
|
# password: "example"
|
||||||
### 注意,对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
|
### 注意,对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
|
||||||
|
|
||||||
|
- name: hysteria2-in-1
|
||||||
|
type: hysteria2
|
||||||
|
port: 10820 # 支持使用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:
|
||||||
|
00000000-0000-0000-0000-000000000000: PASSWORD_0
|
||||||
|
00000000-0000-0000-0000-000000000001: PASSWORD_1
|
||||||
|
# certificate: ./server.crt
|
||||||
|
# private-key: ./server.key
|
||||||
|
## up 和 down 均不写或为 0 则使用 BBR 流控
|
||||||
|
# up: "30 Mbps" # 若不写单位,默认为 Mbps
|
||||||
|
# down: "200 Mbps" # 若不写单位,默认为 Mbps
|
||||||
|
# obfs: salamander # 默认为空,如果填写则开启 obfs,目前仅支持 salamander
|
||||||
|
# obfs-password: yourpassword
|
||||||
|
# max-idle-time: 15000
|
||||||
|
# alpn:
|
||||||
|
# - h3
|
||||||
|
# ignore-client-bandwidth: false
|
||||||
|
# HTTP3 服务器认证失败时的行为 (URL 字符串配置),如果 masquerade 未配置,则返回 404 页
|
||||||
|
# masquerade: file:///var/www # 作为文件服务器
|
||||||
|
# masquerade: http://127.0.0.1:8080 #作为反向代理
|
||||||
|
# masquerade: https://127.0.0.1:8080 #作为反向代理
|
||||||
|
|
||||||
- name: tun-in-1
|
- name: tun-in-1
|
||||||
type: tun
|
type: tun
|
||||||
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
# rule: sub-rule-name1 # 默认使用 rules,如果未找到 sub-rule 则直接使用 rules
|
||||||
|
|
11
go.mod
11
go.mod
|
@ -20,19 +20,19 @@ require (
|
||||||
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
|
||||||
github.com/metacubex/bart v0.19.0
|
github.com/metacubex/bart v0.19.0
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
|
||||||
github.com/metacubex/chacha v0.1.1
|
github.com/metacubex/chacha v0.1.2
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
|
||||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996
|
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996
|
||||||
github.com/metacubex/randv2 v0.2.0
|
github.com/metacubex/randv2 v0.2.0
|
||||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629
|
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925
|
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8
|
github.com/metacubex/sing-shadowsocks v0.2.8
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
github.com/metacubex/sing-shadowsocks2 v0.2.2
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04
|
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63
|
||||||
|
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5
|
||||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
|
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
|
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4
|
github.com/metacubex/utls v1.6.8-alpha.6
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
|
||||||
github.com/miekg/dns v1.1.63
|
github.com/miekg/dns v1.1.63
|
||||||
github.com/mroth/weightedrand/v2 v2.1.0
|
github.com/mroth/weightedrand/v2 v2.1.0
|
||||||
|
@ -44,7 +44,6 @@ require (
|
||||||
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
|
||||||
github.com/sagernet/sing v0.5.2
|
github.com/sagernet/sing v0.5.2
|
||||||
github.com/sagernet/sing-mux v0.2.1
|
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/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||||
github.com/samber/lo v1.49.1
|
github.com/samber/lo v1.49.1
|
||||||
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
||||||
|
|
22
go.sum
22
go.sum
|
@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY
|
||||||
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
|
||||||
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||||
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
|
||||||
|
@ -111,26 +111,26 @@ github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/j
|
||||||
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
|
||||||
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||||
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
|
||||||
github.com/metacubex/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 h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
|
||||||
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
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-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
|
||||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
|
||||||
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
|
||||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
|
||||||
|
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
|
||||||
|
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||||
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
|
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 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-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 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
github.com/metacubex/utls v1.6.8-alpha.6 h1:5ZdZNiZFkKKgEcuPOOROIc8bA4dX2VJHoY3gajSnSaU=
|
||||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
github.com/metacubex/utls v1.6.8-alpha.6/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
|
||||||
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||||
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
|
||||||
|
@ -174,8 +174,6 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
|
||||||
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
|
||||||
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
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-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||||
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
|
|
||||||
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
|
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||||
|
|
|
@ -221,7 +221,7 @@ func updateExperimental(c *config.Experimental) {
|
||||||
if c.QUICGoDisableECN {
|
if c.QUICGoDisableECN {
|
||||||
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
|
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
|
||||||
}
|
}
|
||||||
dialer.GetIP4PEnable(c.IP4PEnable)
|
resolver.SetIP4PEnable(c.IP4PEnable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateNTP(c *config.NTP) {
|
func updateNTP(c *config.NTP) {
|
||||||
|
|
|
@ -86,6 +86,11 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if len(tlsConfig.Certificates) > 0 {
|
||||||
|
l = tls.NewListener(l, tlsConfig)
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("disallow using AnyTLS without certificates config")
|
||||||
|
}
|
||||||
sl.listeners = append(sl.listeners, l)
|
sl.listeners = append(sl.listeners, l)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -130,8 +135,6 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
|
||||||
|
|
||||||
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
conn = tls.Server(conn, l.tlsConfig)
|
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
b := buf.NewPacket()
|
b := buf.NewPacket()
|
||||||
|
|
|
@ -3,9 +3,9 @@ package config
|
||||||
import (
|
import (
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/nnip"
|
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
|
"go4.org/netipx"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -50,6 +50,10 @@ type Tun struct {
|
||||||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
|
||||||
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
|
||||||
|
ExcludeSrcPort []uint16 `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
|
||||||
|
ExcludeSrcPortRange []string `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
|
||||||
|
ExcludeDstPort []uint16 `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
|
||||||
|
ExcludeDstPortRange []string `yaml:"exclude-dst-port-range" json:"exclude-dst-port-range,omitempty"`
|
||||||
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
|
||||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||||
|
@ -66,11 +70,11 @@ type Tun struct {
|
||||||
func (t *Tun) Sort() {
|
func (t *Tun) Sort() {
|
||||||
slices.Sort(t.DNSHijack)
|
slices.Sort(t.DNSHijack)
|
||||||
|
|
||||||
slices.SortFunc(t.Inet4Address, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet4Address, netipx.ComparePrefix)
|
||||||
slices.SortFunc(t.Inet6Address, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet6Address, netipx.ComparePrefix)
|
||||||
slices.SortFunc(t.RouteAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.RouteAddress, netipx.ComparePrefix)
|
||||||
slices.Sort(t.RouteAddressSet)
|
slices.Sort(t.RouteAddressSet)
|
||||||
slices.SortFunc(t.RouteExcludeAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.RouteExcludeAddress, netipx.ComparePrefix)
|
||||||
slices.Sort(t.RouteExcludeAddressSet)
|
slices.Sort(t.RouteExcludeAddressSet)
|
||||||
slices.Sort(t.IncludeInterface)
|
slices.Sort(t.IncludeInterface)
|
||||||
slices.Sort(t.ExcludeInterface)
|
slices.Sort(t.ExcludeInterface)
|
||||||
|
@ -82,10 +86,10 @@ func (t *Tun) Sort() {
|
||||||
slices.Sort(t.IncludePackage)
|
slices.Sort(t.IncludePackage)
|
||||||
slices.Sort(t.ExcludePackage)
|
slices.Sort(t.ExcludePackage)
|
||||||
|
|
||||||
slices.SortFunc(t.Inet4RouteAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet4RouteAddress, netipx.ComparePrefix)
|
||||||
slices.SortFunc(t.Inet6RouteAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet6RouteAddress, netipx.ComparePrefix)
|
||||||
slices.SortFunc(t.Inet4RouteExcludeAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet4RouteExcludeAddress, netipx.ComparePrefix)
|
||||||
slices.SortFunc(t.Inet6RouteExcludeAddress, nnip.PrefixCompare)
|
slices.SortFunc(t.Inet6RouteExcludeAddress, netipx.ComparePrefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tun) Equal(other Tun) bool {
|
func (t *Tun) Equal(other Tun) bool {
|
||||||
|
|
|
@ -78,7 +78,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
63
listener/inbound/anytls_test.go
Normal file
63
listener/inbound/anytls_test.go
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testInboundAnyTLS(t *testing.T, inboundOptions inbound.AnyTLSOption, outboundOptions outbound.AnyTLSOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "anytls_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Users = map[string]string{"test": userUUID}
|
||||||
|
in, err := inbound.NewAnyTLS(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "anytls_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.Password = userUUID
|
||||||
|
|
||||||
|
out, err := outbound.NewAnyTLS(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundAnyTLS_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.AnyTLSOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.AnyTLSOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundAnyTLS(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
251
listener/inbound/common_test.go
Normal file
251
listener/inbound/common_test.go
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
|
"github.com/metacubex/mihomo/common/utils"
|
||||||
|
"github.com/metacubex/mihomo/component/ca"
|
||||||
|
"github.com/metacubex/mihomo/component/generater"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/render"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var httpPath = "/inbound_test"
|
||||||
|
var httpData = make([]byte, 10240)
|
||||||
|
var remoteAddr = netip.MustParseAddr("1.2.3.4")
|
||||||
|
var userUUID = utils.NewUUIDV4().String()
|
||||||
|
var tlsCertificate, tlsPrivateKey, tlsFingerprint, _ = N.NewRandomTLSKeyPair()
|
||||||
|
var tlsConfigCert, _ = tls.X509KeyPair([]byte(tlsCertificate), []byte(tlsPrivateKey))
|
||||||
|
var tlsConfig = &tls.Config{Certificates: []tls.Certificate{tlsConfigCert}, NextProtos: []string{"h2", "http/1.1"}}
|
||||||
|
var tlsClientConfig, _ = ca.GetTLSConfig(nil, tlsFingerprint, "", "")
|
||||||
|
var realityPrivateKey, realityPublickey string
|
||||||
|
var realityDest = "itunes.apple.com"
|
||||||
|
var realityShortid = "10f897e26c4b9478"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Read(httpData)
|
||||||
|
privateKey, err := generater.GeneratePrivateKey()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
publicKey := privateKey.PublicKey()
|
||||||
|
realityPrivateKey = base64.RawURLEncoding.EncodeToString(privateKey[:])
|
||||||
|
realityPublickey = base64.RawURLEncoding.EncodeToString(publicKey[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestTunnel struct {
|
||||||
|
HandleTCPConnFn func(conn net.Conn, metadata *C.Metadata)
|
||||||
|
HandleUDPPacketFn func(packet C.UDPPacket, metadata *C.Metadata)
|
||||||
|
NatTableFn func() C.NatTable
|
||||||
|
CloseFn func() error
|
||||||
|
DoTestFn func(t *testing.T, proxy C.ProxyAdapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *TestTunnel) HandleTCPConn(conn net.Conn, metadata *C.Metadata) {
|
||||||
|
tt.HandleTCPConnFn(conn, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *TestTunnel) HandleUDPPacket(packet C.UDPPacket, metadata *C.Metadata) {
|
||||||
|
tt.HandleUDPPacketFn(packet, metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *TestTunnel) NatTable() C.NatTable {
|
||||||
|
return tt.NatTableFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *TestTunnel) Close() error {
|
||||||
|
return tt.CloseFn()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tt *TestTunnel) DoTest(t *testing.T, proxy C.ProxyAdapter) {
|
||||||
|
tt.DoTestFn(t, proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestTunnelListener struct {
|
||||||
|
ch chan net.Conn
|
||||||
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
addr net.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestTunnelListener) Accept() (net.Conn, error) {
|
||||||
|
select {
|
||||||
|
case conn, ok := <-t.ch:
|
||||||
|
if !ok {
|
||||||
|
return nil, net.ErrClosed
|
||||||
|
}
|
||||||
|
return conn, nil
|
||||||
|
case <-t.ctx.Done():
|
||||||
|
return nil, t.ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestTunnelListener) Close() error {
|
||||||
|
t.cancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TestTunnelListener) Addr() net.Addr {
|
||||||
|
return t.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
type WaitCloseConn struct {
|
||||||
|
net.Conn
|
||||||
|
ch chan struct{}
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *WaitCloseConn) Close() error {
|
||||||
|
err := c.Conn.Close()
|
||||||
|
c.once.Do(func() {
|
||||||
|
close(c.ch)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ C.Tunnel = (*TestTunnel)(nil)
|
||||||
|
var _ net.Listener = (*TestTunnelListener)(nil)
|
||||||
|
|
||||||
|
func NewHttpTestTunnel() *TestTunnel {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
ln := &TestTunnelListener{ch: make(chan net.Conn), ctx: ctx, cancel: cancel, addr: net.TCPAddrFromAddrPort(netip.AddrPortFrom(remoteAddr, 0))}
|
||||||
|
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Get(httpPath, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Data(w, r, httpData)
|
||||||
|
})
|
||||||
|
go http.Serve(ln, r)
|
||||||
|
testFn := func(t *testing.T, proxy C.ProxyAdapter, proto string) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s://%s%s", proto, remoteAddr, httpPath), nil)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req = req.WithContext(ctx)
|
||||||
|
|
||||||
|
var dstPort uint16 = 80
|
||||||
|
if proto == "https" {
|
||||||
|
dstPort = 443
|
||||||
|
}
|
||||||
|
metadata := &C.Metadata{
|
||||||
|
NetWork: C.TCP,
|
||||||
|
DstIP: remoteAddr,
|
||||||
|
DstPort: dstPort,
|
||||||
|
}
|
||||||
|
instance, err := proxy.DialContext(ctx, metadata)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer instance.Close()
|
||||||
|
|
||||||
|
transport := &http.Transport{
|
||||||
|
DialContext: func(context.Context, string, string) (net.Conn, error) {
|
||||||
|
return instance, nil
|
||||||
|
},
|
||||||
|
// from http.DefaultTransport
|
||||||
|
MaxIdleConns: 100,
|
||||||
|
IdleConnTimeout: 90 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
// for our self-signed cert
|
||||||
|
TLSClientConfig: tlsClientConfig.Clone(),
|
||||||
|
// open http2
|
||||||
|
ForceAttemptHTTP2: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
client := http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
Transport: transport,
|
||||||
|
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||||
|
return http.ErrUseLastResponse
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
defer client.CloseIdleConnections()
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assert.Equal(t, httpData, data)
|
||||||
|
}
|
||||||
|
tunnel := &TestTunnel{
|
||||||
|
HandleTCPConnFn: func(conn net.Conn, metadata *C.Metadata) {
|
||||||
|
defer conn.Close()
|
||||||
|
if metadata.DstIP != remoteAddr && metadata.Host != realityDest {
|
||||||
|
return // not match, just return
|
||||||
|
}
|
||||||
|
c := &WaitCloseConn{
|
||||||
|
Conn: conn,
|
||||||
|
ch: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if metadata.DstPort == 443 {
|
||||||
|
tlsConn := tls.Server(c, tlsConfig.Clone())
|
||||||
|
if metadata.Host == realityDest { // ignore the tls handshake error for realityDest
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ln.ch <- tlsConn
|
||||||
|
} else {
|
||||||
|
ln.ch <- c
|
||||||
|
}
|
||||||
|
<-c.ch
|
||||||
|
},
|
||||||
|
CloseFn: ln.Close,
|
||||||
|
DoTestFn: func(t *testing.T, proxy C.ProxyAdapter) {
|
||||||
|
// Sequential testing for debugging
|
||||||
|
t.Run("Sequential", func(t *testing.T) {
|
||||||
|
testFn(t, proxy, "http")
|
||||||
|
testFn(t, proxy, "https")
|
||||||
|
})
|
||||||
|
|
||||||
|
// Concurrent testing to detect stress
|
||||||
|
t.Run("Concurrent", func(t *testing.T) {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
const num = 50
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
testFn(t, proxy, "https")
|
||||||
|
defer wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
for i := 0; i < num; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
testFn(t, proxy, "http")
|
||||||
|
defer wg.Done()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return tunnel
|
||||||
|
}
|
93
listener/inbound/hysteria2_test.go
Normal file
93
listener/inbound/hysteria2_test.go
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testInboundHysteria2(t *testing.T, inboundOptions inbound.Hysteria2Option, outboundOptions outbound.Hysteria2Option) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "hysteria2_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Users = map[string]string{"test": userUUID}
|
||||||
|
in, err := inbound.NewHysteria2(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "hysteria2_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.Password = userUUID
|
||||||
|
|
||||||
|
out, err := outbound.NewHysteria2(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundHysteria2_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.Hysteria2Option{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.Hysteria2Option{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundHysteria2_Salamander(t *testing.T) {
|
||||||
|
inboundOptions := inbound.Hysteria2Option{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
Obfs: "salamander",
|
||||||
|
ObfsPassword: userUUID,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.Hysteria2Option{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Obfs: "salamander",
|
||||||
|
ObfsPassword: userUUID,
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundHysteria2_Brutal(t *testing.T) {
|
||||||
|
inboundOptions := inbound.Hysteria2Option{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
Up: "30 Mbps",
|
||||||
|
Down: "200 Mbps",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.Hysteria2Option{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Up: "30 Mbps",
|
||||||
|
Down: "200 Mbps",
|
||||||
|
}
|
||||||
|
testInboundHysteria2(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
44
listener/inbound/mux_test.go
Normal file
44
listener/inbound/mux_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var singMuxProtocolList = []string{"h2mux", "smux"} // don't test "yamux" because it has some confused bugs
|
||||||
|
|
||||||
|
// notCloseProxyAdapter is a proxy adapter that does not close the underlying outbound.ProxyAdapter.
|
||||||
|
// The outbound.SingMux will close the underlying outbound.ProxyAdapter when it is closed, but we don't want to close it.
|
||||||
|
// The underlying outbound.ProxyAdapter should only be closed by the caller of testSingMux.
|
||||||
|
type notCloseProxyAdapter struct {
|
||||||
|
outbound.ProxyAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notCloseProxyAdapter) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSingMux(t *testing.T, tunnel *TestTunnel, out outbound.ProxyAdapter) {
|
||||||
|
t.Run("singmux", func(t *testing.T) {
|
||||||
|
for _, protocol := range singMuxProtocolList {
|
||||||
|
protocol := protocol
|
||||||
|
t.Run(protocol, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
singMuxOption := outbound.SingMuxOption{
|
||||||
|
Enabled: true,
|
||||||
|
Protocol: protocol,
|
||||||
|
}
|
||||||
|
out, err := outbound.NewSingMux(singMuxOption, ¬CloseProxyAdapter{out})
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
98
listener/inbound/shadowsocks_test.go
Normal file
98
listener/inbound/shadowsocks_test.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"net/netip"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
|
||||||
|
shadowsocks "github.com/metacubex/sing-shadowsocks"
|
||||||
|
"github.com/metacubex/sing-shadowsocks/shadowaead"
|
||||||
|
"github.com/metacubex/sing-shadowsocks/shadowaead_2022"
|
||||||
|
"github.com/metacubex/sing-shadowsocks/shadowstream"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var shadowsocksCipherList = []string{shadowsocks.MethodNone}
|
||||||
|
var shadowsocksPassword32 string
|
||||||
|
var shadowsocksPassword16 string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
shadowsocksCipherList = append(shadowsocksCipherList, shadowaead.List...)
|
||||||
|
shadowsocksCipherList = append(shadowsocksCipherList, shadowaead_2022.List...)
|
||||||
|
shadowsocksCipherList = append(shadowsocksCipherList, shadowstream.List...)
|
||||||
|
passwordBytes := make([]byte, 32)
|
||||||
|
rand.Read(passwordBytes)
|
||||||
|
shadowsocksPassword32 = base64.StdEncoding.EncodeToString(passwordBytes)
|
||||||
|
shadowsocksPassword16 = base64.StdEncoding.EncodeToString(passwordBytes[:16])
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInboundShadowSocks(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
|
||||||
|
t.Parallel()
|
||||||
|
for _, cipher := range shadowsocksCipherList {
|
||||||
|
cipher := cipher
|
||||||
|
t.Run(cipher, func(t *testing.T) {
|
||||||
|
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
|
||||||
|
inboundOptions.Cipher = cipher
|
||||||
|
outboundOptions.Cipher = cipher
|
||||||
|
testInboundShadowSocks0(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInboundShadowSocks0(t *testing.T, inboundOptions inbound.ShadowSocksOption, outboundOptions outbound.ShadowSocksOption) {
|
||||||
|
t.Parallel()
|
||||||
|
password := shadowsocksPassword32
|
||||||
|
if strings.Contains(inboundOptions.Cipher, "-128-") {
|
||||||
|
password = shadowsocksPassword16
|
||||||
|
}
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "shadowsocks_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Password = password
|
||||||
|
in, err := inbound.NewShadowSocks(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "shadowsocks_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.Password = password
|
||||||
|
|
||||||
|
out, err := outbound.NewShadowSocks(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
|
||||||
|
testSingMux(t, tunnel, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundShadowSocks_Basic(t *testing.T) {
|
||||||
|
inboundOptions := inbound.ShadowSocksOption{}
|
||||||
|
outboundOptions := outbound.ShadowSocksOption{}
|
||||||
|
testInboundShadowSocks(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
219
listener/inbound/trojan_test.go
Normal file
219
listener/inbound/trojan_test.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testInboundTrojan(t *testing.T, inboundOptions inbound.TrojanOption, outboundOptions outbound.TrojanOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "trojan_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Users = []inbound.TrojanUser{
|
||||||
|
{Username: "test", Password: userUUID},
|
||||||
|
}
|
||||||
|
in, err := inbound.NewTrojan(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "trojan_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.Password = userUUID
|
||||||
|
|
||||||
|
out, err := outbound.NewTrojan(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
|
||||||
|
testSingMux(t, tunnel, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Wss1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Wss2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Grpc1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Grpc2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Reality(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
SNI: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Reality_Grpc(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
SNI: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_TLS_TrojanSS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
SSOption: inbound.TrojanSSOption{
|
||||||
|
Enabled: true,
|
||||||
|
Method: "",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
SSOpts: outbound.TrojanSSOption{
|
||||||
|
Enabled: true,
|
||||||
|
Method: "",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTrojan_Wss_TrojanSS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TrojanOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
SSOption: inbound.TrojanSSOption{
|
||||||
|
Enabled: true,
|
||||||
|
Method: "",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TrojanOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
SSOpts: outbound.TrojanSSOption{
|
||||||
|
Enabled: true,
|
||||||
|
Method: "",
|
||||||
|
Password: "password",
|
||||||
|
},
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundTrojan(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
92
listener/inbound/tuic_test.go
Normal file
92
listener/inbound/tuic_test.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tuicCCs = []string{"cubic", "new_reno", "bbr"}
|
||||||
|
|
||||||
|
func testInboundTuic(t *testing.T, inboundOptions inbound.TuicOption, outboundOptions outbound.TuicOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.Users = map[string]string{userUUID: userUUID}
|
||||||
|
inboundOptions.Token = []string{userUUID}
|
||||||
|
|
||||||
|
for _, tuicCC := range tuicCCs {
|
||||||
|
tuicCC := tuicCC
|
||||||
|
t.Run(tuicCC, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
t.Run("v4", func(t *testing.T) {
|
||||||
|
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
|
||||||
|
outboundOptions.Token = userUUID
|
||||||
|
outboundOptions.CongestionController = tuicCC
|
||||||
|
inboundOptions.CongestionController = tuicCC
|
||||||
|
testInboundTuic0(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
t.Run("v5", func(t *testing.T) {
|
||||||
|
inboundOptions, outboundOptions := inboundOptions, outboundOptions // don't modify outside options value
|
||||||
|
outboundOptions.UUID = userUUID
|
||||||
|
outboundOptions.Password = userUUID
|
||||||
|
outboundOptions.CongestionController = tuicCC
|
||||||
|
inboundOptions.CongestionController = tuicCC
|
||||||
|
testInboundTuic0(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInboundTuic0(t *testing.T, inboundOptions inbound.TuicOption, outboundOptions outbound.TuicOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "tuic_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
in, err := inbound.NewTuic(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "tuic_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
|
||||||
|
out, err := outbound.NewTuic(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundTuic_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.TuicOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
AuthenticationTimeout: 5000,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.TuicOption{
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundTuic(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
|
@ -39,6 +39,10 @@ type TunOption struct {
|
||||||
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
|
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
|
||||||
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
|
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
|
||||||
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
|
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
|
||||||
|
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
|
||||||
|
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
|
||||||
|
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
|
||||||
|
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
|
||||||
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
|
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
|
||||||
IncludePackage []string `inbound:"include-package,omitempty"`
|
IncludePackage []string `inbound:"include-package,omitempty"`
|
||||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||||
|
@ -137,6 +141,10 @@ func NewTun(options *TunOption) (*Tun, error) {
|
||||||
IncludeUIDRange: options.IncludeUIDRange,
|
IncludeUIDRange: options.IncludeUIDRange,
|
||||||
ExcludeUID: options.ExcludeUID,
|
ExcludeUID: options.ExcludeUID,
|
||||||
ExcludeUIDRange: options.ExcludeUIDRange,
|
ExcludeUIDRange: options.ExcludeUIDRange,
|
||||||
|
ExcludeSrcPort: options.ExcludeSrcPort,
|
||||||
|
ExcludeSrcPortRange: options.ExcludeSrcPortRange,
|
||||||
|
ExcludeDstPort: options.ExcludeDstPort,
|
||||||
|
ExcludeDstPortRange: options.ExcludeDstPortRange,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
|
|
195
listener/inbound/vless_test.go
Normal file
195
listener/inbound/vless_test.go
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testInboundVless(t *testing.T, inboundOptions inbound.VlessOption, outboundOptions outbound.VlessOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "vless_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Users = []inbound.VlessUser{
|
||||||
|
{Username: "test", UUID: userUUID, Flow: "xtls-rprx-vision"},
|
||||||
|
}
|
||||||
|
in, err := inbound.NewVless(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "vless_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.UUID = userUUID
|
||||||
|
|
||||||
|
out, err := outbound.NewVless(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
|
||||||
|
testSingMux(t, tunnel, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Wss1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Wss2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Grpc1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Grpc2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Reality(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
ServerName: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
t.Run("xtls-rprx-vision", func(t *testing.T) {
|
||||||
|
outboundOptions.Flow = "xtls-rprx-vision"
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVless_Reality_Grpc(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VlessOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VlessOption{
|
||||||
|
TLS: true,
|
||||||
|
ServerName: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVless(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
257
listener/inbound/vmess_test.go
Normal file
257
listener/inbound/vmess_test.go
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
package inbound_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metacubex/mihomo/adapter/outbound"
|
||||||
|
"github.com/metacubex/mihomo/listener/inbound"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testInboundVMess(t *testing.T, inboundOptions inbound.VmessOption, outboundOptions outbound.VmessOption) {
|
||||||
|
t.Parallel()
|
||||||
|
inboundOptions.BaseOption = inbound.BaseOption{
|
||||||
|
NameStr: "vmess_inbound",
|
||||||
|
Listen: "127.0.0.1",
|
||||||
|
Port: "0",
|
||||||
|
}
|
||||||
|
inboundOptions.Users = []inbound.VmessUser{
|
||||||
|
{Username: "test", UUID: userUUID, AlterID: 0},
|
||||||
|
}
|
||||||
|
in, err := inbound.NewVmess(&inboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnel := NewHttpTestTunnel()
|
||||||
|
defer tunnel.Close()
|
||||||
|
|
||||||
|
err = in.Listen(tunnel)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
addrPort, err := netip.ParseAddrPort(in.Address())
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
outboundOptions.Name = "vmess_outbound"
|
||||||
|
outboundOptions.Server = addrPort.Addr().String()
|
||||||
|
outboundOptions.Port = int(addrPort.Port())
|
||||||
|
outboundOptions.UUID = userUUID
|
||||||
|
outboundOptions.AlterID = 0
|
||||||
|
outboundOptions.Cipher = "auto"
|
||||||
|
|
||||||
|
out, err := outbound.NewVmess(outboundOptions)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
tunnel.DoTest(t, out)
|
||||||
|
|
||||||
|
testSingMux(t, tunnel, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Basic(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{}
|
||||||
|
outboundOptions := outbound.VmessOption{}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_TLS(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Ws(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Ws_ed1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws?ed=2048",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Ws_ed2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
MaxEarlyData: 2048,
|
||||||
|
EarlyDataHeaderName: "Sec-WebSocket-Protocol",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Ws_Upgrade1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
V2rayHttpUpgrade: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Ws_Upgrade2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
V2rayHttpUpgrade: true,
|
||||||
|
V2rayHttpUpgradeFastOpen: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Wss1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Wss2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "ws",
|
||||||
|
WSOpts: outbound.WSOptions{
|
||||||
|
Path: "/ws",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Grpc1(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Grpc2(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
Certificate: tlsCertificate,
|
||||||
|
PrivateKey: tlsPrivateKey,
|
||||||
|
WsPath: "/ws",
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
Fingerprint: tlsFingerprint,
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Reality(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
ServerName: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInboundVMess_Reality_Grpc(t *testing.T) {
|
||||||
|
inboundOptions := inbound.VmessOption{
|
||||||
|
RealityConfig: inbound.RealityConfig{
|
||||||
|
Dest: net.JoinHostPort(realityDest, "443"),
|
||||||
|
PrivateKey: realityPrivateKey,
|
||||||
|
ShortID: []string{realityShortid},
|
||||||
|
ServerNames: []string{realityDest},
|
||||||
|
},
|
||||||
|
GrpcServiceName: "GunService",
|
||||||
|
}
|
||||||
|
outboundOptions := outbound.VmessOption{
|
||||||
|
TLS: true,
|
||||||
|
ServerName: realityDest,
|
||||||
|
RealityOpts: outbound.RealityOptions{
|
||||||
|
PublicKey: realityPublickey,
|
||||||
|
ShortID: realityShortid,
|
||||||
|
},
|
||||||
|
ClientFingerprint: "chrome",
|
||||||
|
Network: "grpc",
|
||||||
|
GrpcOpts: outbound.GrpcOptions{GrpcServiceName: "GunService"},
|
||||||
|
}
|
||||||
|
testInboundVMess(t, inboundOptions, outboundOptions)
|
||||||
|
}
|
|
@ -3,8 +3,6 @@ package inner
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
N "github.com/metacubex/mihomo/common/net"
|
N "github.com/metacubex/mihomo/common/net"
|
||||||
C "github.com/metacubex/mihomo/constant"
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
@ -16,9 +14,13 @@ func New(t C.Tunnel) {
|
||||||
tunnel = t
|
tunnel = t
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleTcp(address string, proxy string) (conn net.Conn, err error) {
|
func GetTunnel() C.Tunnel {
|
||||||
|
return tunnel
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleTcp(tunnel C.Tunnel, address string, proxy string) (conn net.Conn, err error) {
|
||||||
if tunnel == nil {
|
if tunnel == nil {
|
||||||
return nil, errors.New("tcp uninitialized")
|
return nil, errors.New("tunnel uninitialized")
|
||||||
}
|
}
|
||||||
// executor Parsed
|
// executor Parsed
|
||||||
conn1, conn2 := N.Pipe()
|
conn1, conn2 := N.Pipe()
|
||||||
|
@ -31,15 +33,8 @@ func HandleTcp(address string, proxy string) (conn net.Conn, err error) {
|
||||||
if proxy != "" {
|
if proxy != "" {
|
||||||
metadata.SpecialProxy = proxy
|
metadata.SpecialProxy = proxy
|
||||||
}
|
}
|
||||||
if h, port, err := net.SplitHostPort(address); err == nil {
|
if err = metadata.SetRemoteAddress(address); err != nil {
|
||||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
return nil, err
|
||||||
metadata.DstPort = uint16(port)
|
|
||||||
}
|
|
||||||
if ip, err := netip.ParseAddr(h); err == nil {
|
|
||||||
metadata.DstIP = ip
|
|
||||||
} else {
|
|
||||||
metadata.Host = h
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go tunnel.HandleTCPConn(conn2, metadata)
|
go tunnel.HandleTCPConn(conn2, metadata)
|
||||||
|
|
|
@ -73,7 +73,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,13 +9,15 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
"github.com/metacubex/mihomo/listener/inner"
|
"github.com/metacubex/mihomo/listener/inner"
|
||||||
|
"github.com/metacubex/mihomo/log"
|
||||||
"github.com/metacubex/mihomo/ntp"
|
"github.com/metacubex/mihomo/ntp"
|
||||||
|
|
||||||
"github.com/metacubex/reality"
|
utls "github.com/metacubex/utls"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conn = reality.Conn
|
type Conn = utls.Conn
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Dest string
|
Dest string
|
||||||
|
@ -26,13 +28,14 @@ type Config struct {
|
||||||
Proxy string
|
Proxy string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) Build() (*Builder, error) {
|
func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
|
||||||
realityConfig := &reality.Config{}
|
realityConfig := &utls.RealityConfig{}
|
||||||
realityConfig.SessionTicketsDisabled = true
|
realityConfig.SessionTicketsDisabled = true
|
||||||
realityConfig.Type = "tcp"
|
realityConfig.Type = "tcp"
|
||||||
realityConfig.Dest = c.Dest
|
realityConfig.Dest = c.Dest
|
||||||
realityConfig.Time = ntp.Now
|
realityConfig.Time = ntp.Now
|
||||||
realityConfig.ServerNames = make(map[string]bool)
|
realityConfig.ServerNames = make(map[string]bool)
|
||||||
|
realityConfig.Log = log.Debugln
|
||||||
for _, it := range c.ServerNames {
|
for _, it := range c.ServerNames {
|
||||||
realityConfig.ServerNames[it] = true
|
realityConfig.ServerNames[it] = true
|
||||||
}
|
}
|
||||||
|
@ -50,7 +53,11 @@ func (c Config) Build() (*Builder, error) {
|
||||||
realityConfig.ShortIds = make(map[[8]byte]bool)
|
realityConfig.ShortIds = make(map[[8]byte]bool)
|
||||||
for i, shortIDString := range c.ShortID {
|
for i, shortIDString := range c.ShortID {
|
||||||
var shortID [8]byte
|
var shortID [8]byte
|
||||||
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString))
|
decodedLen := hex.DecodedLen(len(shortIDString))
|
||||||
|
if decodedLen > 8 {
|
||||||
|
return nil, fmt.Errorf("invalid short_id[%d]: %s", i, shortIDString)
|
||||||
|
}
|
||||||
|
decodedLen, err = hex.Decode(shortID[:], []byte(shortIDString))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err)
|
return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err)
|
||||||
}
|
}
|
||||||
|
@ -61,18 +68,18 @@ func (c Config) Build() (*Builder, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
|
realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||||
return inner.HandleTcp(address, c.Proxy)
|
return inner.HandleTcp(tunnel, address, c.Proxy)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Builder{realityConfig}, nil
|
return &Builder{realityConfig}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
realityConfig *reality.Config
|
realityConfig *utls.RealityConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b Builder) NewListener(l net.Listener) net.Listener {
|
func (b Builder) NewListener(l net.Listener) net.Listener {
|
||||||
l = reality.NewListener(l, b.realityConfig)
|
l = utls.NewRealityListener(l, b.realityConfig)
|
||||||
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
|
||||||
// We fixed it by calling Close() directly.
|
// We fixed it by calling Close() directly.
|
||||||
l = realityListenerWrapper{l}
|
l = realityListenerWrapper{l}
|
||||||
|
@ -80,7 +87,7 @@ func (b Builder) NewListener(l net.Listener) net.Listener {
|
||||||
}
|
}
|
||||||
|
|
||||||
type realityConnWrapper struct {
|
type realityConnWrapper struct {
|
||||||
*reality.Conn
|
*utls.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c realityConnWrapper) Upstream() any {
|
func (c realityConnWrapper) Upstream() any {
|
||||||
|
@ -100,5 +107,5 @@ func (l realityListenerWrapper) Accept() (net.Conn, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return realityConnWrapper{c.(*reality.Conn)}, nil
|
return realityConnWrapper{c.(*utls.Conn)}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ func NewListenerHandler(lc ListenerConfig) (h *ListenerHandler, err error) {
|
||||||
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
|
||||||
return ctx
|
return ctx
|
||||||
},
|
},
|
||||||
Logger: log.SingLogger,
|
Logger: log.SingInfoToDebugLogger, // convert sing-mux info log to debug
|
||||||
Handler: h,
|
Handler: h,
|
||||||
Padding: lc.MuxOption.Padding,
|
Padding: lc.MuxOption.Padding,
|
||||||
Brutal: mux.BrutalOptions{
|
Brutal: mux.BrutalOptions{
|
||||||
|
|
|
@ -196,7 +196,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
|
||||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||||
err := l.service.NewConnection(ctx, conn, M.Metadata{
|
err := l.service.NewConnection(ctx, conn, M.Metadata{
|
||||||
Protocol: "shadowsocks",
|
Protocol: "shadowsocks",
|
||||||
Source: M.ParseSocksaddr(conn.RemoteAddr().String()),
|
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
LC "github.com/metacubex/mihomo/listener/config"
|
LC "github.com/metacubex/mihomo/listener/config"
|
||||||
"github.com/metacubex/mihomo/listener/sing"
|
"github.com/metacubex/mihomo/listener/sing"
|
||||||
"github.com/metacubex/mihomo/log"
|
"github.com/metacubex/mihomo/log"
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
|
||||||
tun "github.com/metacubex/sing-tun"
|
tun "github.com/metacubex/sing-tun"
|
||||||
"github.com/metacubex/sing-tun/control"
|
"github.com/metacubex/sing-tun/control"
|
||||||
|
@ -211,6 +212,22 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||||
return nil, E.Cause(err, "parse exclude_uid_range")
|
return nil, E.Cause(err, "parse exclude_uid_range")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
excludeSrcPort := uidToRange(options.ExcludeSrcPort)
|
||||||
|
if len(options.ExcludeSrcPortRange) > 0 {
|
||||||
|
var err error
|
||||||
|
excludeSrcPort, err = parseRange(excludeSrcPort, options.ExcludeSrcPortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse exclude_src_port_range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
excludeDstPort := uidToRange(options.ExcludeDstPort)
|
||||||
|
if len(options.ExcludeDstPortRange) > 0 {
|
||||||
|
var err error
|
||||||
|
excludeDstPort, err = parseRange(excludeDstPort, options.ExcludeDstPortRange)
|
||||||
|
if err != nil {
|
||||||
|
return nil, E.Cause(err, "parse exclude_dst_port_range")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var dnsAdds []netip.AddrPort
|
var dnsAdds []netip.AddrPort
|
||||||
|
|
||||||
|
@ -339,6 +356,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
||||||
ExcludeInterface: options.ExcludeInterface,
|
ExcludeInterface: options.ExcludeInterface,
|
||||||
IncludeUID: includeUID,
|
IncludeUID: includeUID,
|
||||||
ExcludeUID: excludeUID,
|
ExcludeUID: excludeUID,
|
||||||
|
ExcludeSrcPort: excludeSrcPort,
|
||||||
|
ExcludeDstPort: excludeDstPort,
|
||||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||||
IncludePackage: options.IncludePackage,
|
IncludePackage: options.IncludePackage,
|
||||||
ExcludePackage: options.ExcludePackage,
|
ExcludePackage: options.ExcludePackage,
|
||||||
|
@ -566,13 +585,13 @@ func (d *cDialerInterfaceFinder) FindInterfaceName(destination netip.Addr) strin
|
||||||
return "<invalid>"
|
return "<invalid>"
|
||||||
}
|
}
|
||||||
|
|
||||||
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
func uidToRange[T constraints.Integer](uidList []T) []ranges.Range[T] {
|
||||||
return common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
|
return common.Map(uidList, func(uid T) ranges.Range[T] {
|
||||||
return ranges.NewSingle(uid)
|
return ranges.NewSingle(uid)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) {
|
func parseRange[T constraints.Integer](uidRanges []ranges.Range[T], rangeList []string) ([]ranges.Range[T], error) {
|
||||||
for _, uidRange := range rangeList {
|
for _, uidRange := range rangeList {
|
||||||
if !strings.Contains(uidRange, ":") {
|
if !strings.Contains(uidRange, ":") {
|
||||||
return nil, E.New("missing ':' in range: ", uidRange)
|
return nil, E.New("missing ':' in range: ", uidRange)
|
||||||
|
@ -593,7 +612,7 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, E.Cause(err, "parse range end")
|
return nil, E.Cause(err, "parse range end")
|
||||||
}
|
}
|
||||||
uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end)))
|
uidRanges = append(uidRanges, ranges.New(T(start), T(end)))
|
||||||
}
|
}
|
||||||
return uidRanges, nil
|
return uidRanges, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,7 +106,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
|
||||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||||
Protocol: "vless",
|
Protocol: "vless",
|
||||||
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
|
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
|
@ -90,7 +90,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -187,7 +187,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
|
||||||
ctx := sing.WithAdditions(context.TODO(), additions...)
|
ctx := sing.WithAdditions(context.TODO(), additions...)
|
||||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||||
Protocol: "vmess",
|
Protocol: "vmess",
|
||||||
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
|
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = conn.Close()
|
_ = conn.Close()
|
||||||
|
|
|
@ -72,7 +72,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
|
||||||
}
|
}
|
||||||
|
|
||||||
dscp, _ := getDSCP(oob[:oobn])
|
dscp, _ := getDSCP(oob[:oobn])
|
||||||
additions = append(additions, inbound.WithDSCP(dscp))
|
additions := append(additions, inbound.WithDSCP(dscp)) // don't change outside additions
|
||||||
|
|
||||||
if rAddr.Addr().Is4() {
|
if rAddr.Addr().Is4() {
|
||||||
// try to unmap 4in6 address
|
// try to unmap 4in6 address
|
||||||
|
|
|
@ -84,7 +84,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
||||||
if tlsConfig.Certificates != nil {
|
if tlsConfig.Certificates != nil {
|
||||||
return nil, errors.New("certificate is unavailable in reality")
|
return nil, errors.New("certificate is unavailable in reality")
|
||||||
}
|
}
|
||||||
realityBuilder, err = config.RealityConfig.Build()
|
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,14 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
||||||
} else {
|
} else {
|
||||||
tlsConfig.NextProtos = []string{"h3"}
|
tlsConfig.NextProtos = []string{"h3"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.MaxIdleTime == 0 {
|
||||||
|
config.MaxIdleTime = 15000
|
||||||
|
}
|
||||||
|
if config.AuthenticationTimeout == 0 {
|
||||||
|
config.AuthenticationTimeout = 1000
|
||||||
|
}
|
||||||
|
|
||||||
quicConfig := &quic.Config{
|
quicConfig := &quic.Config{
|
||||||
MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond,
|
MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond,
|
||||||
MaxIncomingStreams: ServerMaxIncomingStreams,
|
MaxIncomingStreams: ServerMaxIncomingStreams,
|
||||||
|
|
13
log/sing.go
13
log/sing.go
|
@ -65,4 +65,17 @@ func (l singLogger) Panic(args ...any) {
|
||||||
Fatalln(fmt.Sprint(args...))
|
Fatalln(fmt.Sprint(args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type singInfoToDebugLogger struct {
|
||||||
|
singLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l singInfoToDebugLogger) InfoContext(ctx context.Context, args ...any) {
|
||||||
|
Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l singInfoToDebugLogger) Info(args ...any) {
|
||||||
|
Debugln(fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
|
||||||
var SingLogger L.ContextLogger = singLogger{}
|
var SingLogger L.ContextLogger = singLogger{}
|
||||||
|
var SingInfoToDebugLogger L.ContextLogger = singInfoToDebugLogger{}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/metacubex/mihomo/common/atomic"
|
"github.com/metacubex/mihomo/common/atomic"
|
||||||
"github.com/metacubex/mihomo/common/buf"
|
"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/padding"
|
||||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||||
"github.com/metacubex/mihomo/transport/vmess"
|
"github.com/metacubex/mihomo/transport/vmess"
|
||||||
|
@ -83,12 +82,7 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
|
||||||
b.WriteZeroN(paddingLen)
|
b.WriteZeroN(paddingLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
getTlsConn := func() (net.Conn, error) {
|
tlsConn, err := vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
|
||||||
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
|
||||||
defer cancel()
|
|
||||||
return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
|
|
||||||
}
|
|
||||||
tlsConn, err := getTlsConn()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -40,7 +40,7 @@ func newStream(id uint32, sess *Session) *Stream {
|
||||||
// Read implements net.Conn
|
// Read implements net.Conn
|
||||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||||
n, err = s.pipeR.Read(b)
|
n, err = s.pipeR.Read(b)
|
||||||
if s.dieErr != nil {
|
if n == 0 && s.dieErr != nil {
|
||||||
err = s.dieErr
|
err = s.dieErr
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptrace"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -21,6 +22,7 @@ import (
|
||||||
"github.com/metacubex/mihomo/common/buf"
|
"github.com/metacubex/mihomo/common/buf"
|
||||||
"github.com/metacubex/mihomo/common/pool"
|
"github.com/metacubex/mihomo/common/pool"
|
||||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||||
|
C "github.com/metacubex/mihomo/constant"
|
||||||
|
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
@ -35,17 +37,17 @@ var defaultHeader = http.Header{
|
||||||
"user-agent": []string{"grpc-go/1.36.0"},
|
"user-agent": []string{"grpc-go/1.36.0"},
|
||||||
}
|
}
|
||||||
|
|
||||||
type DialFn = func(network, addr string) (net.Conn, error)
|
type DialFn = func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||||
|
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
initFn func() (io.ReadCloser, error)
|
initFn func() (io.ReadCloser, netAddr, error)
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
flusher http.Flusher
|
closer io.Closer
|
||||||
netAddr
|
netAddr
|
||||||
|
|
||||||
reader io.ReadCloser
|
reader io.ReadCloser
|
||||||
once sync.Once
|
once sync.Once
|
||||||
close atomic.Bool
|
closed atomic.Bool
|
||||||
err error
|
err error
|
||||||
remain int
|
remain int
|
||||||
br *bufio.Reader
|
br *bufio.Reader
|
||||||
|
@ -60,7 +62,7 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Conn) initReader() {
|
func (g *Conn) initReader() {
|
||||||
reader, err := g.initFn()
|
reader, addr, err := g.initFn()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.err = err
|
g.err = err
|
||||||
if closer, ok := g.writer.(io.Closer); ok {
|
if closer, ok := g.writer.(io.Closer); ok {
|
||||||
|
@ -68,8 +70,9 @@ func (g *Conn) initReader() {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
g.netAddr = addr
|
||||||
|
|
||||||
if !g.close.Load() {
|
if !g.closed.Load() {
|
||||||
g.reader = reader
|
g.reader = reader
|
||||||
g.br = bufio.NewReader(reader)
|
g.br = bufio.NewReader(reader)
|
||||||
} else {
|
} else {
|
||||||
|
@ -147,8 +150,8 @@ func (g *Conn) Write(b []byte) (n int, err error) {
|
||||||
err = g.err
|
err = g.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.flusher != nil {
|
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||||
g.flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return len(b), err
|
return len(b), err
|
||||||
|
@ -170,8 +173,8 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
||||||
err = g.err
|
err = g.err
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.flusher != nil {
|
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||||
g.flusher.Flush()
|
flusher.Flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -182,15 +185,28 @@ func (g *Conn) FrontHeadroom() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Conn) Close() error {
|
func (g *Conn) Close() error {
|
||||||
g.close.Store(true)
|
g.closed.Store(true)
|
||||||
|
var errorArr []error
|
||||||
|
|
||||||
if reader := g.reader; reader != nil {
|
if reader := g.reader; reader != nil {
|
||||||
reader.Close()
|
if err := reader.Close(); err != nil {
|
||||||
|
errorArr = append(errorArr, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if closer, ok := g.writer.(io.Closer); ok {
|
if closer, ok := g.writer.(io.Closer); ok {
|
||||||
return closer.Close()
|
if err := closer.Close(); err != nil {
|
||||||
|
errorArr = append(errorArr, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
if closer := g.closer; closer != nil {
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
errorArr = append(errorArr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errorArr...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
|
func (g *Conn) SetReadDeadline(t time.Time) error { return g.SetDeadline(t) }
|
||||||
|
@ -208,24 +224,26 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
||||||
wrap := TransportWrap{}
|
|
||||||
|
|
||||||
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
dialFunc := func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
|
||||||
pconn, err := dialFn(network, addr)
|
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
||||||
|
defer cancel()
|
||||||
|
pconn, err := dialFn(ctx, network, addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
wrap.remoteAddr = pconn.RemoteAddr()
|
|
||||||
wrap.localAddr = pconn.LocalAddr()
|
|
||||||
|
|
||||||
if tlsConfig == nil {
|
if tlsConfig == nil {
|
||||||
return pconn, nil
|
return pconn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(Fingerprint) != 0 {
|
clientFingerprint := clientFingerprint
|
||||||
|
if tlsC.HaveGlobalFingerprint() && len(clientFingerprint) == 0 {
|
||||||
|
clientFingerprint = tlsC.GetGlobalFingerprint()
|
||||||
|
}
|
||||||
|
if len(clientFingerprint) != 0 {
|
||||||
if realityConfig == nil {
|
if realityConfig == nil {
|
||||||
if fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
|
||||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||||
if err := utlsConn.HandshakeContext(ctx); err != nil {
|
if err := utlsConn.HandshakeContext(ctx); err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
|
@ -239,7 +257,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
||||||
return utlsConn, nil
|
return utlsConn, nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
|
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pconn.Close()
|
pconn.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -269,7 +287,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
wrap.Transport = &http2.Transport{
|
transport := &http2.Transport{
|
||||||
DialTLSContext: dialFunc,
|
DialTLSContext: dialFunc,
|
||||||
TLSClientConfig: tlsConfig,
|
TLSClientConfig: tlsConfig,
|
||||||
AllowHTTP: false,
|
AllowHTTP: false,
|
||||||
|
@ -277,7 +295,13 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
||||||
PingTimeout: 0,
|
PingTimeout: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &wrap
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
wrap := &TransportWrap{
|
||||||
|
Transport: transport,
|
||||||
|
ctx: ctx,
|
||||||
|
cancel: cancel,
|
||||||
|
}
|
||||||
|
return wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, error) {
|
func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, error) {
|
||||||
|
@ -302,17 +326,25 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
||||||
ProtoMinor: 0,
|
ProtoMinor: 0,
|
||||||
Header: defaultHeader,
|
Header: defaultHeader,
|
||||||
}
|
}
|
||||||
|
request = request.WithContext(transport.ctx)
|
||||||
|
|
||||||
conn := &Conn{
|
conn := &Conn{
|
||||||
initFn: func() (io.ReadCloser, error) {
|
initFn: func() (io.ReadCloser, netAddr, error) {
|
||||||
|
nAddr := netAddr{}
|
||||||
|
trace := &httptrace.ClientTrace{
|
||||||
|
GotConn: func(connInfo httptrace.GotConnInfo) {
|
||||||
|
nAddr.localAddr = connInfo.Conn.LocalAddr()
|
||||||
|
nAddr.remoteAddr = connInfo.Conn.RemoteAddr()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
request = request.WithContext(httptrace.WithClientTrace(request.Context(), trace))
|
||||||
response, err := transport.RoundTrip(request)
|
response, err := transport.RoundTrip(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nAddr, err
|
||||||
}
|
}
|
||||||
return response.Body, nil
|
return response.Body, nAddr, nil
|
||||||
},
|
},
|
||||||
writer: writer,
|
writer: writer,
|
||||||
netAddr: transport.netAddr,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go conn.Init()
|
go conn.Init()
|
||||||
|
@ -320,10 +352,17 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
||||||
}
|
}
|
||||||
|
|
||||||
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
func StreamGunWithConn(conn net.Conn, tlsConfig *tls.Config, cfg *Config, realityConfig *tlsC.RealityConfig) (net.Conn, error) {
|
||||||
dialFn := func(network, addr string) (net.Conn, error) {
|
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
transport := NewHTTP2Client(dialFn, tlsConfig, cfg.ClientFingerprint, realityConfig)
|
||||||
return StreamGunWithTransport(transport, cfg)
|
c, err := StreamGunWithTransport(transport, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c, ok := c.(*Conn); ok { // The incoming net.Conn should be closed synchronously with the generated gun.Conn
|
||||||
|
c.closer = conn
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,21 +43,22 @@ func NewServerHandler(options ServerOption) http.Handler {
|
||||||
writer.WriteHeader(http.StatusOK)
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
conn := &Conn{
|
conn := &Conn{
|
||||||
initFn: func() (io.ReadCloser, error) {
|
initFn: func() (io.ReadCloser, netAddr, error) {
|
||||||
return request.Body, nil
|
nAddr := netAddr{}
|
||||||
|
if request.RemoteAddr != "" {
|
||||||
|
metadata := C.Metadata{}
|
||||||
|
if err := metadata.SetRemoteAddress(request.RemoteAddr); err == nil {
|
||||||
|
nAddr.remoteAddr = net.TCPAddrFromAddrPort(metadata.AddrPort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if addr, ok := request.Context().Value(http.LocalAddrContextKey).(net.Addr); ok {
|
||||||
|
nAddr.localAddr = addr
|
||||||
|
}
|
||||||
|
return request.Body, nAddr, nil
|
||||||
},
|
},
|
||||||
writer: writer,
|
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
|
|
||||||
}
|
}
|
||||||
|
_ = conn.Init()
|
||||||
|
|
||||||
wrapper := &h2ConnWrapper{
|
wrapper := &h2ConnWrapper{
|
||||||
// gun.Conn can't correct handle ReadDeadline
|
// gun.Conn can't correct handle ReadDeadline
|
||||||
|
|
|
@ -1,21 +1,26 @@
|
||||||
package gun
|
package gun
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/http2"
|
"context"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransportWrap struct {
|
type TransportWrap struct {
|
||||||
*http2.Transport
|
*http2.Transport
|
||||||
netAddr
|
ctx context.Context
|
||||||
|
cancel context.CancelFunc
|
||||||
|
closeOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tw *TransportWrap) RemoteAddr() net.Addr {
|
func (tw *TransportWrap) Close() error {
|
||||||
return tw.remoteAddr
|
tw.closeOnce.Do(func() {
|
||||||
}
|
tw.cancel()
|
||||||
|
closeTransport(tw.Transport)
|
||||||
func (tw *TransportWrap) LocalAddr() net.Addr {
|
})
|
||||||
return tw.localAddr
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type netAddr struct {
|
type netAddr struct {
|
||||||
|
@ -23,10 +28,10 @@ type netAddr struct {
|
||||||
localAddr net.Addr
|
localAddr net.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (addr *netAddr) RemoteAddr() net.Addr {
|
func (addr netAddr) RemoteAddr() net.Addr {
|
||||||
return addr.remoteAddr
|
return addr.remoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (addr *netAddr) LocalAddr() net.Addr {
|
func (addr netAddr) LocalAddr() net.Addr {
|
||||||
return addr.localAddr
|
return addr.localAddr
|
||||||
}
|
}
|
||||||
|
|
64
transport/gun/transport_close.go
Normal file
64
transport/gun/transport_close.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package gun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientConnPool struct {
|
||||||
|
t *http2.Transport
|
||||||
|
|
||||||
|
mu sync.Mutex
|
||||||
|
conns map[string][]*http2.ClientConn // key is host:port
|
||||||
|
dialing map[string]unsafe.Pointer // currently in-flight dials
|
||||||
|
keys map[*http2.ClientConn][]string
|
||||||
|
addConnCalls map[string]unsafe.Pointer // in-flight addConnIfNeeded calls
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientConn struct {
|
||||||
|
t *http.Transport
|
||||||
|
tconn net.Conn // usually *tls.Conn, except specialized impls
|
||||||
|
}
|
||||||
|
|
||||||
|
type efaceWords struct {
|
||||||
|
typ unsafe.Pointer
|
||||||
|
data unsafe.Pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsConn interface {
|
||||||
|
net.Conn
|
||||||
|
NetConn() net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeClientConn(cc *http2.ClientConn) { // like forceCloseConn() in http2.ClientConn but also apply for tls-like conn
|
||||||
|
if conn, ok := (*clientConn)(unsafe.Pointer(cc)).tconn.(tlsConn); ok {
|
||||||
|
t := time.AfterFunc(time.Second, func() {
|
||||||
|
_ = conn.NetConn().Close()
|
||||||
|
})
|
||||||
|
defer t.Stop()
|
||||||
|
}
|
||||||
|
_ = cc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeTransport(tr *http2.Transport) {
|
||||||
|
connPool := transportConnPool(tr)
|
||||||
|
p := (*clientConnPool)((*efaceWords)(unsafe.Pointer(&connPool)).data)
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
for _, vv := range p.conns {
|
||||||
|
for _, cc := range vv {
|
||||||
|
closeClientConn(cc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// cleanup
|
||||||
|
p.conns = make(map[string][]*http2.ClientConn)
|
||||||
|
p.keys = make(map[*http2.ClientConn][]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:linkname transportConnPool golang.org/x/net/http2.(*Transport).connPool
|
||||||
|
func transportConnPool(t *http2.Transport) http2.ClientConnPool
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue