mirror of
https://github.com/MetaCubeX/Clash.Meta.git
synced 2025-04-19 16:50: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
|
||||
tags:
|
||||
- "v*"
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
branches:
|
||||
- Alpha
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
func NewProxy(adapter C.ProxyAdapter) *Proxy {
|
||||
return &Proxy{
|
||||
ProxyAdapter: adapter,
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"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.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
|
||||
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]))
|
||||
case socks5.AtypIPv6:
|
||||
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
||||
metadata.DstIP = ip6.Unmap()
|
||||
metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv6len])
|
||||
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
|
||||
}
|
||||
metadata.DstIP = metadata.DstIP.Unmap()
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -12,7 +11,6 @@ import (
|
|||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
@ -52,7 +50,7 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
|
|||
if err != nil {
|
||||
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) {
|
||||
|
@ -73,7 +71,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||
metadata.DstIP = ip
|
||||
}
|
||||
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
|
||||
|
@ -88,6 +86,11 @@ func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
|
|||
return info
|
||||
}
|
||||
|
||||
// Close implements C.ProxyAdapter
|
||||
func (t *AnyTLS) Close() error {
|
||||
return t.client.Close()
|
||||
}
|
||||
|
||||
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
|
@ -111,9 +114,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
|||
if tlsConfig.Host == "" {
|
||||
tlsConfig.Host = option.Server
|
||||
}
|
||||
if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
|
||||
tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
||||
}
|
||||
tOption.TLSConfig = tlsConfig
|
||||
|
||||
outbound := &AnyTLS{
|
||||
|
@ -132,9 +132,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
|
|||
option: &option,
|
||||
dialer: singDialer,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
|
||||
_ = o.client.Close()
|
||||
})
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
|
|
@ -4,15 +4,23 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/utils"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
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 {
|
||||
name string
|
||||
addr string
|
||||
|
@ -152,6 +160,10 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
|
|||
return opts
|
||||
}
|
||||
|
||||
func (b *Base) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type BasicOption struct {
|
||||
TFO bool `proxy:"tfo,omitempty"`
|
||||
MPTCP bool `proxy:"mptcp,omitempty"`
|
||||
|
@ -221,6 +233,10 @@ func (c *conn) ReaderReplaceable() bool {
|
|||
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 {
|
||||
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
|
||||
|
@ -267,6 +283,10 @@ func (c *packetConn) ReaderReplaceable() bool {
|
|||
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 {
|
||||
epc := N.NewEnhancePacketConn(pc)
|
||||
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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"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 c, nil
|
||||
|
@ -99,7 +99,12 @@ func (h *Http) ProxyInfo() C.ProxyInfo {
|
|||
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()
|
||||
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
|
||||
tempHeaders := map[string]string{
|
||||
|
@ -123,13 +128,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
|
|||
|
||||
HeaderString += "\r\n"
|
||||
|
||||
_, err := rw.Write([]byte(HeaderString))
|
||||
_, err = c.Write([]byte(HeaderString))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(rw), nil)
|
||||
resp, err := http.ReadResponse(bufio.NewReader(c), nil)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -15,7 +14,6 @@ import (
|
|||
"github.com/metacubex/quic-go/congestion"
|
||||
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/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
|
@ -45,8 +43,6 @@ type Hysteria struct {
|
|||
|
||||
option *HysteriaOption
|
||||
client *core.Client
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
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 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) {
|
||||
|
@ -63,7 +59,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
|
|||
if err != nil {
|
||||
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 {
|
||||
|
@ -82,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
|
|||
return cDialer.ListenPacket(ctx, network, "", rAddrPort)
|
||||
},
|
||||
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,
|
||||
client: client,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria)
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeHysteria(h *Hysteria) {
|
||||
// Close implements C.ProxyAdapter
|
||||
func (h *Hysteria) Close() error {
|
||||
if h.client != nil {
|
||||
_ = h.client.Close()
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
return h.client.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type hyPacketConn struct {
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -39,8 +38,6 @@ type Hysteria2 struct {
|
|||
option *Hysteria2Option
|
||||
client *hysteria2.Client
|
||||
dialer proxydialer.SingDialer
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type Hysteria2Option struct {
|
||||
|
@ -78,7 +75,7 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||
if err != nil {
|
||||
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) {
|
||||
|
@ -91,16 +88,15 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
|||
if pc == 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 {
|
||||
_ = h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
if h.closeCh != nil {
|
||||
close(h.closeCh)
|
||||
return h.client.CloseWithError(errors.New("proxy removed"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProxyInfo implements C.ProxyAdapter
|
||||
|
@ -175,7 +171,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||
CWND: option.CWND,
|
||||
UdpMTU: option.UdpMTU,
|
||||
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 {
|
||||
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 {
|
||||
|
@ -226,7 +222,6 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
|
|||
client: client,
|
||||
dialer: singDialer,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeHysteria2)
|
||||
|
||||
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"
|
||||
"fmt"
|
||||
"net"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
|
@ -62,7 +61,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
|
||||
}
|
||||
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), m), nil
|
||||
return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil
|
||||
}
|
||||
|
||||
// SupportUOT implements C.ProxyAdapter
|
||||
|
@ -141,16 +140,17 @@ func NewMieru(option MieruOption) (*Mieru, error) {
|
|||
option: &option,
|
||||
client: c,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeMieru)
|
||||
return outbound, nil
|
||||
}
|
||||
|
||||
func closeMieru(m *Mieru) {
|
||||
// Close implements C.ProxyAdapter
|
||||
func (m *Mieru) Close() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if m.client != nil && m.client.IsRunning() {
|
||||
m.client.Stop()
|
||||
return m.client.Stop()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {
|
||||
|
|
|
@ -20,16 +20,19 @@ func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
|
|||
config := new(tlsC.RealityConfig)
|
||||
|
||||
const x25519ScalarSize = 32
|
||||
var publicKey [x25519ScalarSize]byte
|
||||
n, err := base64.RawURLEncoding.Decode(publicKey[:], []byte(o.PublicKey))
|
||||
if err != nil || n != x25519ScalarSize {
|
||||
publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
|
||||
if err != nil || len(publicKey) != x25519ScalarSize {
|
||||
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 {
|
||||
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))
|
||||
if err != nil || n > tlsC.RealityMaxShortIDLen {
|
||||
return nil, errors.New("invalid REALITY short ID")
|
||||
|
|
|
@ -100,7 +100,7 @@ type restlsOption struct {
|
|||
}
|
||||
|
||||
// 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
|
||||
switch ss.obfsMode {
|
||||
case "tls":
|
||||
|
@ -109,7 +109,6 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
|
|||
_, port, _ := net.SplitHostPort(ss.addr)
|
||||
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
|
||||
case "websocket":
|
||||
var err error
|
||||
if ss.v2rayOption != nil {
|
||||
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
|
||||
} 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)
|
||||
}
|
||||
case shadowtls.Mode:
|
||||
var err error
|
||||
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
useEarly = true
|
||||
case restls.Mode:
|
||||
var err error
|
||||
c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
|
||||
if err != nil {
|
||||
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 = 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 {
|
||||
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
|
||||
if useEarly {
|
||||
|
@ -197,7 +200,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer)
|
||||
addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -42,12 +42,15 @@ type ShadowSocksROption struct {
|
|||
}
|
||||
|
||||
// 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.cipher.StreamConn(c)
|
||||
var (
|
||||
iv []byte
|
||||
err error
|
||||
iv []byte
|
||||
)
|
||||
switch conn := c.(type) {
|
||||
case *shadowstream.Conn:
|
||||
|
@ -102,7 +105,7 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer)
|
||||
addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package outbound
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"runtime"
|
||||
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
|
@ -18,8 +17,7 @@ import (
|
|||
)
|
||||
|
||||
type SingMux struct {
|
||||
C.ProxyAdapter
|
||||
base ProxyBase
|
||||
ProxyAdapter
|
||||
client *mux.Client
|
||||
dialer proxydialer.SingDialer
|
||||
onlyTcp bool
|
||||
|
@ -43,25 +41,21 @@ type BrutalOption struct {
|
|||
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) {
|
||||
options := s.base.DialOptions(opts...)
|
||||
options := s.ProxyAdapter.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
if err != nil {
|
||||
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) {
|
||||
if s.onlyTcp {
|
||||
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
|
||||
}
|
||||
options := s.base.DialOptions(opts...)
|
||||
options := s.ProxyAdapter.DialOptions(opts...)
|
||||
s.dialer.SetDialer(dialer.NewDialer(options...))
|
||||
|
||||
// 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 {
|
||||
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 {
|
||||
|
@ -103,11 +97,15 @@ func (s *SingMux) ProxyInfo() C.ProxyInfo {
|
|||
return info
|
||||
}
|
||||
|
||||
func closeSingMux(s *SingMux) {
|
||||
_ = s.client.Close()
|
||||
// Close implements C.ProxyAdapter
|
||||
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
|
||||
// "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{
|
||||
ProxyAdapter: proxy,
|
||||
base: base,
|
||||
client: client,
|
||||
dialer: singDialer,
|
||||
onlyTcp: option.OnlyTcp,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeSingMux)
|
||||
return outbound, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"net"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
|
@ -41,7 +42,7 @@ type streamOption struct {
|
|||
obfsOption *simpleObfsOption
|
||||
}
|
||||
|
||||
func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
func snellStreamConn(c net.Conn, option streamOption) *snell.Snell {
|
||||
switch option.obfsOption.Mode {
|
||||
case "tls":
|
||||
c = obfs.NewTLSObfs(c, option.obfsOption.Host)
|
||||
|
@ -54,25 +55,35 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
|
|||
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
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})
|
||||
if metadata.NetWork == C.UDP {
|
||||
err := snell.WriteUDPHeader(c, s.version)
|
||||
return c, err
|
||||
}
|
||||
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
|
||||
c = snellStreamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
err := s.writeHeaderContext(ctx, c, metadata)
|
||||
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
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil {
|
||||
c.Close()
|
||||
if err = s.writeHeaderContext(ctx, c, metadata); err != nil {
|
||||
_ = c.Close()
|
||||
return nil, err
|
||||
}
|
||||
return NewConn(c, s), err
|
||||
|
@ -120,12 +131,8 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
|
||||
|
||||
err = snell.WriteUDPHeader(c, s.version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err = s.StreamConnContext(ctx, c, metadata)
|
||||
|
||||
pc := snell.PacketConn(c)
|
||||
return newPacketConn(pc, s), nil
|
||||
|
@ -212,7 +219,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
|
|||
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
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"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,
|
||||
}
|
||||
}
|
||||
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 c, nil
|
||||
|
@ -135,7 +136,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||
}
|
||||
|
||||
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 {
|
||||
err = fmt.Errorf("client hanshake error: %w", err)
|
||||
return
|
||||
|
@ -147,7 +148,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||
err = errors.New("invalid UDP bind address")
|
||||
return
|
||||
} else if bindUDPAddr.IP.IsUnspecified() {
|
||||
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr())
|
||||
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr(), C.IPv4Prefer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -178,6 +179,14 @@ func (ss *Socks5) ProxyInfo() C.ProxyInfo {
|
|||
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) {
|
||||
var tlsConfig *tls.Config
|
||||
if option.TLS {
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
@ -25,7 +24,10 @@ type Ssh struct {
|
|||
*Base
|
||||
|
||||
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 {
|
||||
|
@ -49,7 +51,7 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
client, err := s.client.connect(ctx, cDialer, s.addr)
|
||||
client, err := s.connect(ctx, cDialer, s.addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -58,16 +60,10 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return NewConn(N.NewRefConn(c, s), s), nil
|
||||
return NewConn(c, s), nil
|
||||
}
|
||||
|
||||
type sshClient struct {
|
||||
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) {
|
||||
func (s *Ssh) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
|
||||
s.cMutex.Lock()
|
||||
defer s.cMutex.Unlock()
|
||||
if s.client != nil {
|
||||
|
@ -108,7 +104,15 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
|
|||
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()
|
||||
defer s.cMutex.Unlock()
|
||||
if s.client != nil {
|
||||
|
@ -117,17 +121,6 @@ func (s *sshClient) Close() error {
|
|||
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) {
|
||||
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
|
||||
|
||||
|
@ -204,11 +197,8 @@ func NewSsh(option SshOption) (*Ssh, error) {
|
|||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
option: &option,
|
||||
client: &sshClient{
|
||||
config: &config,
|
||||
},
|
||||
config: &config,
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeSsh)
|
||||
|
||||
return outbound, nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/ca"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
|
@ -17,12 +18,13 @@ import (
|
|||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/shadowsocks/core"
|
||||
"github.com/metacubex/mihomo/transport/trojan"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
)
|
||||
|
||||
type Trojan struct {
|
||||
*Base
|
||||
instance *trojan.Trojan
|
||||
option *TrojanOption
|
||||
option *TrojanOption
|
||||
hexPassword [trojan.KeyLength]byte
|
||||
|
||||
// for gun mux
|
||||
gunTLSConfig *tls.Config
|
||||
|
@ -60,15 +62,21 @@ type TrojanSSOption struct {
|
|||
Password string `proxy:"password,omitempty"`
|
||||
}
|
||||
|
||||
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) {
|
||||
if t.option.Network == "ws" {
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
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)
|
||||
wsOpts := &trojan.WebsocketOption{
|
||||
|
||||
wsOpts := &vmess.WebsocketConfig{
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: t.option.WSOpts.Path,
|
||||
MaxEarlyData: t.option.WSOpts.MaxEarlyData,
|
||||
EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
|
||||
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
|
||||
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
|
||||
ClientFingerprint: t.option.ClientFingerprint,
|
||||
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
|
||||
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)
|
||||
wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
c, err = vmess.StreamWebsocketConn(ctx, c, wsOpts)
|
||||
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 {
|
||||
c.Close()
|
||||
return t.streamConnContext(ctx, c, metadata)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -171,7 +217,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
|
|||
var c net.Conn
|
||||
|
||||
// grpc transport
|
||||
if t.transport != nil && len(opts) == 0 {
|
||||
if t.transport != nil && dialer.IsZeroOptions(opts) {
|
||||
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
|
||||
if err != nil {
|
||||
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)
|
||||
}(c)
|
||||
|
||||
if t.ssCipher != nil {
|
||||
c = t.ssCipher.StreamConn(c)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
c, err = t.streamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
pc := trojan.NewPacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
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) {
|
||||
safeConnClose(c, err)
|
||||
}(c)
|
||||
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)
|
||||
}
|
||||
|
||||
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
|
||||
c, err = t.StreamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pc := t.instance.PacketConn(c)
|
||||
pc := trojan.NewPacketConn(c)
|
||||
return newPacketConn(pc, t), err
|
||||
}
|
||||
|
||||
|
@ -235,7 +268,7 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
|
|||
|
||||
// ListenPacketOnStreamConn implements C.ProxyAdapter
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -251,22 +284,17 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
|
|||
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) {
|
||||
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{
|
||||
Base: &Base{
|
||||
name: option.Name,
|
||||
|
@ -279,8 +307,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
rmark: option.RoutingMark,
|
||||
prefer: C.NewDNSPrefer(option.IPVersion),
|
||||
},
|
||||
instance: trojan.New(tOption),
|
||||
option: &option,
|
||||
option: &option,
|
||||
hexPassword: trojan.Key(option.Password),
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -288,7 +316,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tOption.Reality = t.realityConfig
|
||||
|
||||
if option.SSOpts.Enabled {
|
||||
if option.SSOpts.Password == "" {
|
||||
|
@ -305,7 +332,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
}
|
||||
|
||||
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 cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
|
||||
if len(t.option.DialerProxy) > 0 {
|
||||
|
@ -314,7 +341,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr)
|
||||
c, err := cDialer.DialContext(ctx, "tcp", t.addr)
|
||||
if err != nil {
|
||||
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{
|
||||
NextProtos: option.ALPN,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: tOption.SkipCertVerify,
|
||||
ServerName: tOption.ServerName,
|
||||
InsecureSkipVerify: option.SkipCertVerify,
|
||||
ServerName: option.SNI,
|
||||
}
|
||||
|
||||
var err error
|
||||
|
@ -334,12 +361,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
|
|||
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.gunConfig = &gun.Config{
|
||||
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||
Host: tOption.ServerName,
|
||||
ServiceName: option.GrpcOpts.GrpcServiceName,
|
||||
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
|
||||
}
|
||||
}
|
||||
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer)
|
||||
udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -3,118 +3,59 @@ package outbound
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"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 {
|
||||
var buf [][]byte
|
||||
addrType := metadata.AddrType()
|
||||
aType := uint8(addrType)
|
||||
p := uint(metadata.DstPort)
|
||||
port := []byte{uint8(p >> 8), uint8(p & 0xff)}
|
||||
switch addrType {
|
||||
case socks5.AtypDomainName:
|
||||
case C.AtypDomainName:
|
||||
lenM := uint8(len(metadata.Host))
|
||||
host := []byte(metadata.Host)
|
||||
buf = [][]byte{{aType, lenM}, host, port}
|
||||
case socks5.AtypIPv4:
|
||||
buf = [][]byte{{socks5.AtypDomainName, lenM}, host, port}
|
||||
case C.AtypIPv4:
|
||||
host := metadata.DstIP.AsSlice()
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
case socks5.AtypIPv6:
|
||||
buf = [][]byte{{socks5.AtypIPv4}, host, port}
|
||||
case C.AtypIPv6:
|
||||
host := metadata.DstIP.AsSlice()
|
||||
buf = [][]byte{{aType}, host, port}
|
||||
buf = [][]byte{{socks5.AtypIPv6}, host, port}
|
||||
}
|
||||
return bytes.Join(buf, nil)
|
||||
}
|
||||
|
||||
func resolveUDPAddr(ctx context.Context, network, address string) (*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) {
|
||||
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
|
||||
}
|
||||
var ip netip.Addr
|
||||
var fallback netip.Addr
|
||||
switch prefer {
|
||||
case C.IPv4Only:
|
||||
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
case C.IPv6Only:
|
||||
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
case C.IPv6Prefer:
|
||||
var ips []netip.Addr
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
default:
|
||||
// C.IPv4Prefer, C.DualStack and other
|
||||
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
|
||||
ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ip, port = resolver.LookupIP4P(ip, port)
|
||||
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/gun"
|
||||
"github.com/metacubex/mihomo/transport/socks5"
|
||||
"github.com/metacubex/mihomo/transport/vless"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
||||
|
@ -76,13 +75,7 @@ type VlessOption struct {
|
|||
ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
|
||||
}
|
||||
|
||||
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
|
||||
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
|
||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
||||
}
|
||||
|
||||
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||
switch v.option.Network {
|
||||
case "ws":
|
||||
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,
|
||||
}
|
||||
|
||||
c, err = vmess.StreamH2Conn(c, h2Opts)
|
||||
c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
default:
|
||||
|
@ -169,10 +162,14 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||
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 v.option.PacketAddr {
|
||||
metadata = &C.Metadata{
|
||||
|
@ -229,9 +226,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
|
|||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -239,7 +237,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP))
|
||||
c, err = v.streamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -284,7 +282,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||
}
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -293,7 +291,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = v.streamConn(c, metadata)
|
||||
c, err = v.streamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vless client error: %v", err)
|
||||
}
|
||||
|
@ -385,19 +383,27 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
|
|||
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 {
|
||||
var addrType byte
|
||||
var addr []byte
|
||||
switch metadata.AddrType() {
|
||||
case socks5.AtypIPv4:
|
||||
case C.AtypIPv4:
|
||||
addrType = vless.AtypIPv4
|
||||
addr = make([]byte, net.IPv4len)
|
||||
copy(addr[:], metadata.DstIP.AsSlice())
|
||||
case socks5.AtypIPv6:
|
||||
case C.AtypIPv6:
|
||||
addrType = vless.AtypIPv6
|
||||
addr = make([]byte, net.IPv6len)
|
||||
copy(addr[:], metadata.DstIP.AsSlice())
|
||||
case socks5.AtypDomainName:
|
||||
case C.AtypDomainName:
|
||||
addrType = vless.AtypDomainName
|
||||
addr = make([]byte, len(metadata.Host)+1)
|
||||
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")
|
||||
}
|
||||
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 cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
|
@ -572,7 +578,7 @@ func NewVless(option VlessOption) (*Vless, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
|
||||
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
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
|
||||
if option.TLS {
|
||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
})
|
||||
}, v.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.ServerName == "" {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsConfig.ServerName = host
|
||||
|
|
|
@ -96,13 +96,7 @@ type WSOptions struct {
|
|||
}
|
||||
|
||||
// StreamConnContext implements C.ProxyAdapter
|
||||
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
|
||||
var err error
|
||||
|
||||
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
|
||||
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
|
||||
}
|
||||
|
||||
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
|
||||
switch v.option.Network {
|
||||
case "ws":
|
||||
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,
|
||||
}
|
||||
|
||||
c, err = mihomoVMess.StreamH2Conn(c, h2Opts)
|
||||
c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
|
||||
case "grpc":
|
||||
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
|
||||
default:
|
||||
|
@ -226,17 +220,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
|
|||
if err != nil {
|
||||
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 v.option.XUDP {
|
||||
var globalID [8]byte
|
||||
if metadata.SourceValid() {
|
||||
globalID = utils.GlobalID(metadata.SourceAddress())
|
||||
}
|
||||
if N.NeedHandshake(c) {
|
||||
if useEarly {
|
||||
conn = v.client.DialEarlyXUDPPacketConn(c,
|
||||
globalID,
|
||||
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()))
|
||||
}
|
||||
} else if v.option.PacketAddr {
|
||||
if N.NeedHandshake(c) {
|
||||
if useEarly {
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
|
||||
} else {
|
||||
|
@ -255,7 +256,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
|||
}
|
||||
conn = packetaddr.NewBindConn(conn)
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
if useEarly {
|
||||
conn = v.client.DialEarlyPacketConn(c,
|
||||
M.SocksaddrFromNet(metadata.UDPAddr()))
|
||||
} else {
|
||||
|
@ -264,7 +265,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if N.NeedHandshake(c) {
|
||||
if useEarly {
|
||||
conn = v.client.DialEarlyConn(c,
|
||||
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
} else {
|
||||
|
@ -280,9 +281,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
|
|||
|
||||
// DialContext implements C.ProxyAdapter
|
||||
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -290,7 +292,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
|
|||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
|
||||
c, err = v.streamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -332,7 +334,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||
}
|
||||
var c net.Conn
|
||||
// gun transport
|
||||
if v.transport != nil && len(opts) == 0 {
|
||||
if v.transport != nil && dialer.IsZeroOptions(opts) {
|
||||
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -341,7 +343,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
|
|||
safeConnClose(c, err)
|
||||
}(c)
|
||||
|
||||
c, err = v.streamConn(c, metadata)
|
||||
c, err = v.streamConnContext(ctx, c, metadata)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("new vmess client error: %v", err)
|
||||
}
|
||||
|
@ -395,6 +397,14 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
|
|||
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
|
||||
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
|
||||
|
@ -470,7 +480,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
|
||||
}
|
||||
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 cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
|
||||
if len(v.option.DialerProxy) > 0 {
|
||||
|
@ -479,7 +489,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr)
|
||||
c, err := cDialer.DialContext(ctx, "tcp", v.addr)
|
||||
if err != nil {
|
||||
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
|
||||
if option.TLS {
|
||||
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{
|
||||
tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: v.option.SkipCertVerify,
|
||||
ServerName: v.option.ServerName,
|
||||
})
|
||||
}, v.option.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if option.ServerName == "" {
|
||||
host, _, _ := net.SplitHostPort(v.addr)
|
||||
tlsConfig.ServerName = host
|
||||
|
|
|
@ -8,14 +8,12 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
CN "github.com/metacubex/mihomo/common/net"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/proxydialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
|
@ -45,7 +43,6 @@ type WireGuard struct {
|
|||
tunDevice wireguard.Device
|
||||
dialer proxydialer.SingDialer
|
||||
resolver resolver.Resolver
|
||||
refP *refProxyAdapter
|
||||
|
||||
initOk atomic.Bool
|
||||
initMutex sync.Mutex
|
||||
|
@ -57,8 +54,6 @@ type WireGuard struct {
|
|||
serverAddrMap map[M.Socksaddr]netip.AddrPort
|
||||
serverAddrTime atomic.TypedValue[time.Time]
|
||||
serverAddrMutex sync.Mutex
|
||||
|
||||
closeCh chan struct{} // for test
|
||||
}
|
||||
|
||||
type WireGuardOption struct {
|
||||
|
@ -173,7 +168,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
|
|||
},
|
||||
dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
|
||||
}
|
||||
runtime.SetFinalizer(outbound, closeWireGuard)
|
||||
|
||||
var reserved [3]uint8
|
||||
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 {
|
||||
nss, err := dns.ParseNameServer(option.Dns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range nss {
|
||||
nss[i].ProxyAdapter = refP
|
||||
nss[i].ProxyAdapter = outbound
|
||||
}
|
||||
outbound.resolver = dns.NewResolver(dns.Config{
|
||||
Main: nss,
|
||||
|
@ -309,7 +301,7 @@ func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.Add
|
|||
if address.Addr.IsValid() {
|
||||
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 {
|
||||
return netip.AddrPort{}, err
|
||||
}
|
||||
|
@ -488,13 +480,12 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
|
|||
return ipcConf, nil
|
||||
}
|
||||
|
||||
func closeWireGuard(w *WireGuard) {
|
||||
// Close implements C.ProxyAdapter
|
||||
func (w *WireGuard) Close() error {
|
||||
if w.device != nil {
|
||||
w.device.Close()
|
||||
}
|
||||
if w.closeCh != nil {
|
||||
close(w.closeCh)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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 {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
w.refP.SetProxyAdapter(w)
|
||||
defer w.refP.ClearProxyAdapter()
|
||||
r = w.resolver
|
||||
}
|
||||
options = append(options, dialer.WithResolver(r))
|
||||
|
@ -523,7 +512,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
|
|||
if conn == 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) {
|
||||
|
@ -536,8 +525,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
|
|||
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
|
||||
r := resolver.DefaultResolver
|
||||
if w.resolver != nil {
|
||||
w.refP.SetProxyAdapter(w)
|
||||
defer w.refP.ClearProxyAdapter()
|
||||
r = w.resolver
|
||||
}
|
||||
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 {
|
||||
return nil, E.New("packetConn is nil")
|
||||
}
|
||||
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil
|
||||
return newPacketConn(pc, w), nil
|
||||
}
|
||||
|
||||
// IsL3Protocol implements C.ProxyAdapter
|
||||
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
|
||||
"github.com/metacubex/mihomo/adapter/outbound"
|
||||
"github.com/metacubex/mihomo/common/structure"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
@ -18,12 +16,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||
}
|
||||
|
||||
var (
|
||||
proxy C.ProxyAdapter
|
||||
proxy outbound.ProxyAdapter
|
||||
err error
|
||||
)
|
||||
switch proxyType {
|
||||
case "ss":
|
||||
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
||||
ssOption := &outbound.ShadowSocksOption{}
|
||||
err = decoder.Decode(mapping, ssOption)
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -56,7 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||
Method: "GET",
|
||||
Path: []string{"/"},
|
||||
},
|
||||
ClientFingerprint: tlsC.GetGlobalFingerprint(),
|
||||
}
|
||||
|
||||
err = decoder.Decode(mapping, vmessOption)
|
||||
|
@ -65,7 +62,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||
}
|
||||
proxy, err = outbound.NewVmess(*vmessOption)
|
||||
case "vless":
|
||||
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
||||
vlessOption := &outbound.VlessOption{}
|
||||
err = decoder.Decode(mapping, vlessOption)
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -79,7 +76,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||
}
|
||||
proxy, err = outbound.NewSnell(*snellOption)
|
||||
case "trojan":
|
||||
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()}
|
||||
trojanOption := &outbound.TrojanOption{}
|
||||
err = decoder.Decode(mapping, trojanOption)
|
||||
if err != nil {
|
||||
break
|
||||
|
@ -170,12 +167,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
|
|||
return nil, err
|
||||
}
|
||||
if muxOption.Enabled {
|
||||
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase))
|
||||
proxy, err = outbound.NewSingMux(*muxOption, proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
proxy = outbound.NewAutoCloseProxyAdapter(proxy)
|
||||
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["cipher"] = "auto"
|
||||
if cipher, ok := values["scy"]; ok && cipher != "" {
|
||||
if cipher, ok := values["scy"].(string); ok && cipher != "" {
|
||||
vmess["cipher"] = cipher
|
||||
}
|
||||
|
||||
if sni, ok := values["sni"]; ok && sni != "" {
|
||||
if sni, ok := values["sni"].(string); ok && sni != "" {
|
||||
vmess["servername"] = sni
|
||||
}
|
||||
|
||||
network, _ := values["net"].(string)
|
||||
network = strings.ToLower(network)
|
||||
if values["type"] == "http" {
|
||||
network = "http"
|
||||
} else if network == "http" {
|
||||
network = "h2"
|
||||
network, ok := values["net"].(string)
|
||||
if ok {
|
||||
network = strings.ToLower(network)
|
||||
if values["type"] == "http" {
|
||||
network = "http"
|
||||
} else if network == "http" {
|
||||
network = "h2"
|
||||
}
|
||||
vmess["network"] = network
|
||||
}
|
||||
vmess["network"] = network
|
||||
|
||||
tls, ok := values["tls"].(string)
|
||||
if ok {
|
||||
|
@ -307,12 +309,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||
case "http":
|
||||
headers := make(map[string]any)
|
||||
httpOpts := make(map[string]any)
|
||||
if host, ok := values["host"]; ok && host != "" {
|
||||
headers["Host"] = []string{host.(string)}
|
||||
if host, ok := values["host"].(string); ok && host != "" {
|
||||
headers["Host"] = []string{host}
|
||||
}
|
||||
httpOpts["path"] = []string{"/"}
|
||||
if path, ok := values["path"]; ok && path != "" {
|
||||
httpOpts["path"] = []string{path.(string)}
|
||||
if path, ok := values["path"].(string); ok && path != "" {
|
||||
httpOpts["path"] = []string{path}
|
||||
}
|
||||
httpOpts["headers"] = headers
|
||||
|
||||
|
@ -321,8 +323,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||
case "h2":
|
||||
headers := make(map[string]any)
|
||||
h2Opts := make(map[string]any)
|
||||
if host, ok := values["host"]; ok && host != "" {
|
||||
headers["Host"] = []string{host.(string)}
|
||||
if host, ok := values["host"].(string); ok && host != "" {
|
||||
headers["Host"] = []string{host}
|
||||
}
|
||||
|
||||
h2Opts["path"] = values["path"]
|
||||
|
@ -334,11 +336,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
|
|||
headers := make(map[string]any)
|
||||
wsOpts := make(map[string]any)
|
||||
wsOpts["path"] = "/"
|
||||
if host, ok := values["host"]; ok && host != "" {
|
||||
headers["Host"] = host.(string)
|
||||
if host, ok := values["host"].(string); ok && host != "" {
|
||||
headers["Host"] = host
|
||||
}
|
||||
if path, ok := values["path"]; ok && path != "" {
|
||||
path := path.(string)
|
||||
if path, ok := values["path"].(string); ok && path != "" {
|
||||
path := path
|
||||
pathURL, err := url.Parse(path)
|
||||
if err == nil {
|
||||
query := pathURL.Query()
|
||||
|
|
|
@ -3,29 +3,37 @@ package net
|
|||
import (
|
||||
"context"
|
||||
"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)) {
|
||||
var (
|
||||
quit = make(chan struct{})
|
||||
interrupt = make(chan error, 1)
|
||||
)
|
||||
go func() {
|
||||
select {
|
||||
case <-quit:
|
||||
interrupt <- nil
|
||||
case <-ctx.Done():
|
||||
// Close the connection, discarding the error
|
||||
_ = conn.Close()
|
||||
interrupt <- ctx.Err()
|
||||
}
|
||||
}()
|
||||
stopc := make(chan struct{})
|
||||
stop := contextutils.AfterFunc(ctx, func() {
|
||||
// Close the connection, discarding the error
|
||||
_ = conn.Close()
|
||||
close(stopc)
|
||||
})
|
||||
return func(inputErr *error) {
|
||||
close(quit)
|
||||
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil {
|
||||
// Return context error to user.
|
||||
inputErr = &ctxErr
|
||||
if !stop() {
|
||||
// The AfterFunc was started, wait for it to complete.
|
||||
<-stopc
|
||||
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)
|
||||
|
||||
func NewRefConn(conn net.Conn, ref any) net.Conn {
|
||||
func NewRefConn(conn net.Conn, ref any) ExtendedConn {
|
||||
return &refConn{conn: NewExtendedConn(conn), ref: ref}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ package net
|
|||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
@ -16,7 +18,11 @@ type Path interface {
|
|||
|
||||
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
|
||||
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))
|
||||
if painTextErr == nil {
|
||||
|
@ -32,10 +38,10 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro
|
|||
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)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
return
|
||||
}
|
||||
template := x509.Certificate{SerialNumber: big.NewInt(1)}
|
||||
certDER, err := x509.CreateCertificate(
|
||||
|
@ -45,14 +51,15 @@ func newRandomTLSKeyPair() (tls.Certificate, error) {
|
|||
&key.PublicKey,
|
||||
key)
|
||||
if err != nil {
|
||||
return tls.Certificate{}, err
|
||||
return
|
||||
}
|
||||
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
||||
|
||||
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
cert, err := x509.ParseCertificate(certDER)
|
||||
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]
|
||||
mux sync.Mutex
|
||||
done bool
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
func (o *Observable[T]) process() {
|
||||
|
@ -31,6 +32,7 @@ func (o *Observable[T]) close() {
|
|||
for _, sub := range o.listener {
|
||||
sub.Close()
|
||||
}
|
||||
close(o.stopCh)
|
||||
}
|
||||
|
||||
func (o *Observable[T]) Subscribe() (Subscription[T], error) {
|
||||
|
@ -59,6 +61,7 @@ func NewObservable[T any](iter Iterable[T]) *Observable[T] {
|
|||
observable := &Observable[T]{
|
||||
iterable: iter,
|
||||
listener: map[Subscription[T]]*Subscriber[T]{},
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
go observable.process()
|
||||
return observable
|
||||
|
|
|
@ -70,9 +70,11 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
|
|||
src := NewObservable[int](iter)
|
||||
data, _ := src.Subscribe()
|
||||
<-data
|
||||
|
||||
_, closed := src.Subscribe()
|
||||
assert.NotNil(t, closed)
|
||||
select {
|
||||
case <-src.stopCh:
|
||||
case <-time.After(time.Second):
|
||||
assert.Fail(t, "timeout not stop")
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Parallel()
|
||||
picker, ctx := WithContext[int](context.Background())
|
||||
picker.Go(sleepAndSend(ctx, 30, 2))
|
||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||
picker.Go(sleepAndSend(ctx, 200, 2))
|
||||
picker.Go(sleepAndSend(ctx, 100, 1))
|
||||
|
||||
number := picker.Wait()
|
||||
assert.NotNil(t, number)
|
||||
|
@ -32,8 +33,9 @@ func TestPicker_Basic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestPicker_Timeout(t *testing.T) {
|
||||
t.Parallel()
|
||||
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
|
||||
picker.Go(sleepAndSend(ctx, 20, 1))
|
||||
picker.Go(sleepAndSend(ctx, 100, 1))
|
||||
|
||||
number := picker.Wait()
|
||||
assert.Equal(t, number, lo.Empty[int]())
|
||||
|
|
|
@ -13,25 +13,26 @@ type call[T any] struct {
|
|||
|
||||
type Single[T any] struct {
|
||||
mux sync.Mutex
|
||||
last time.Time
|
||||
wait time.Duration
|
||||
call *call[T]
|
||||
result *Result[T]
|
||||
}
|
||||
|
||||
type Result[T any] struct {
|
||||
Val T
|
||||
Err error
|
||||
Val T
|
||||
Err error
|
||||
Time time.Time
|
||||
}
|
||||
|
||||
// Do single.Do likes sync.singleFlight
|
||||
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
|
||||
s.mux.Lock()
|
||||
now := time.Now()
|
||||
if now.Before(s.last.Add(s.wait)) {
|
||||
result := s.result
|
||||
if result != nil && time.Since(result.Time) < s.wait {
|
||||
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 {
|
||||
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()
|
||||
|
||||
s.mux.Lock()
|
||||
s.call = nil
|
||||
s.result = &Result[T]{callM.val, callM.err}
|
||||
s.last = now
|
||||
if s.call == callM { // maybe reset when fn is running
|
||||
s.call = nil
|
||||
s.result = &Result[T]{callM.val, callM.err, time.Now()}
|
||||
}
|
||||
s.mux.Unlock()
|
||||
return callM.val, callM.err, false
|
||||
}
|
||||
|
||||
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] {
|
||||
|
|
|
@ -11,12 +11,13 @@ import (
|
|||
)
|
||||
|
||||
func TestBasic(t *testing.T) {
|
||||
single := NewSingle[int](time.Millisecond * 30)
|
||||
t.Parallel()
|
||||
single := NewSingle[int](time.Millisecond * 200)
|
||||
foo := 0
|
||||
shardCount := atomic.NewInt32(0)
|
||||
call := func() (int, error) {
|
||||
foo++
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
time.Sleep(time.Millisecond * 20)
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
|
@ -39,7 +40,8 @@ func TestBasic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestTimer(t *testing.T) {
|
||||
single := NewSingle[int](time.Millisecond * 30)
|
||||
t.Parallel()
|
||||
single := NewSingle[int](time.Millisecond * 200)
|
||||
foo := 0
|
||||
callM := func() (int, error) {
|
||||
foo++
|
||||
|
@ -47,7 +49,7 @@ func TestTimer(t *testing.T) {
|
|||
}
|
||||
|
||||
_, _, _ = single.Do(callM)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
_, _, shard := single.Do(callM)
|
||||
|
||||
assert.Equal(t, 1, foo)
|
||||
|
@ -55,7 +57,8 @@ func TestTimer(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
single := NewSingle[int](time.Millisecond * 30)
|
||||
t.Parallel()
|
||||
single := NewSingle[int](time.Millisecond * 200)
|
||||
foo := 0
|
||||
callM := func() (int, error) {
|
||||
foo++
|
||||
|
|
|
@ -141,7 +141,6 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig = GetGlobalTLSConfig(tlsConfig)
|
||||
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
|
||||
tlsConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/component/iface"
|
||||
|
||||
"github.com/insomniacslk/dhcp/dhcpv4"
|
||||
|
@ -86,12 +85,14 @@ func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []
|
|||
return
|
||||
}
|
||||
|
||||
dnsAddr := make([]netip.Addr, l)
|
||||
for i := 0; i < l; i++ {
|
||||
dnsAddr[i] = nnip.IpToAddr(dns[i])
|
||||
results := make([]netip.Addr, 0, len(dns))
|
||||
for _, ip := range dns {
|
||||
if addr, ok := netip.AddrFromSlice(ip); ok {
|
||||
results = append(results, addr.Unmap())
|
||||
}
|
||||
}
|
||||
|
||||
result <- dnsAddr
|
||||
result <- results
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -7,14 +7,12 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/component/keepalive"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -22,34 +20,16 @@ const (
|
|||
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 (
|
||||
dialMux sync.Mutex
|
||||
IP4PEnable bool
|
||||
actualSingleStackDialContext = serialSingleStackDialContext
|
||||
actualDualStackDialContext = serialDualStackDialContext
|
||||
tcpConcurrent = false
|
||||
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) {
|
||||
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) {
|
||||
cfg := applyOptions(options...)
|
||||
opt := applyOptions(options...)
|
||||
|
||||
lc := &net.ListenConfig{}
|
||||
if cfg.addrReuse {
|
||||
if opt.addrReuse {
|
||||
addrReuseToListenConfig(lc)
|
||||
}
|
||||
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
|
||||
socketHookToListenConfig(lc)
|
||||
} else {
|
||||
interfaceName := cfg.interfaceName
|
||||
if interfaceName == "" {
|
||||
if opt.interfaceName == "" {
|
||||
opt.interfaceName = DefaultInterface.Load()
|
||||
}
|
||||
if opt.interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
||||
opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
|
||||
}
|
||||
}
|
||||
if rAddrPort.Addr().Unmap().IsLoopback() {
|
||||
// avoid "The requested address is not valid in its context."
|
||||
interfaceName = ""
|
||||
opt.interfaceName = ""
|
||||
}
|
||||
if interfaceName != "" {
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToListenConfig
|
||||
if cfg.fallbackBind {
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToListenConfig
|
||||
}
|
||||
addr, err := bind(interfaceName, lc, network, address, rAddrPort)
|
||||
addr, err := bind(opt.interfaceName, lc, network, address, rAddrPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
address = addr
|
||||
}
|
||||
if cfg.routingMark != 0 {
|
||||
bindMarkToListenConfig(cfg.routingMark, lc, network, address)
|
||||
if opt.routingMark == 0 {
|
||||
opt.routingMark = int(DefaultRoutingMark.Load())
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
bindMarkToListenConfig(opt.routingMark, lc, network, address)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,11 +121,9 @@ func GetTcpConcurrent() bool {
|
|||
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
|
||||
if IP4PEnable {
|
||||
destination, port = lookupIP4P(destination, port)
|
||||
}
|
||||
destination, port = resolver.LookupIP4P(destination, port)
|
||||
address = net.JoinHostPort(destination.String(), port)
|
||||
|
||||
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)
|
||||
socketHookToToDialer(dialer)
|
||||
} else {
|
||||
interfaceName := opt.interfaceName // don't change the "opt", it's a pointer
|
||||
if interfaceName == "" {
|
||||
if opt.interfaceName == "" {
|
||||
opt.interfaceName = DefaultInterface.Load()
|
||||
}
|
||||
if opt.interfaceName == "" {
|
||||
if finder := DefaultInterfaceFinder.Load(); finder != nil {
|
||||
interfaceName = finder.FindInterfaceName(destination)
|
||||
opt.interfaceName = finder.FindInterfaceName(destination)
|
||||
}
|
||||
}
|
||||
if interfaceName != "" {
|
||||
if opt.interfaceName != "" {
|
||||
bind := bindIfaceToDialer
|
||||
if opt.fallbackBind {
|
||||
bind = fallbackBindIfaceToDialer
|
||||
}
|
||||
if err := bind(interfaceName, dialer, network, destination); err != nil {
|
||||
if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if opt.routingMark == 0 {
|
||||
opt.routingMark = int(DefaultRoutingMark.Load())
|
||||
}
|
||||
if opt.routingMark != 0 {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return parallelDialContext(ctx, 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)
|
||||
if len(ipv4s) == 0 && len(ipv6s) == 0 {
|
||||
return nil, ErrorNoIpAddress
|
||||
|
@ -289,7 +277,7 @@ loop:
|
|||
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 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
|
@ -328,7 +316,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
|
|||
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 {
|
||||
return nil, ErrorNoIpAddress
|
||||
}
|
||||
|
@ -394,23 +382,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
|
|||
|
||||
func NewDialer(options ...Option) Dialer {
|
||||
opt := applyOptions(options...)
|
||||
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
|
||||
return Dialer{Opt: opt}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
DefaultOptions []Option
|
||||
DefaultInterface = atomic.NewTypedValue[string]("")
|
||||
DefaultRoutingMark = atomic.NewInt32(0)
|
||||
|
||||
|
@ -115,3 +114,15 @@ func WithOption(o option) Option {
|
|||
*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"
|
||||
"sync"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/component/profile/cachefile"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"go4.org/netipx"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -183,7 +184,7 @@ func New(options Options) (*Pool, error) {
|
|||
hostAddr = options.IPNet.Masked().Addr()
|
||||
gateway = hostAddr.Next()
|
||||
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) {
|
||||
|
|
|
@ -69,7 +69,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
|
|||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
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
|
||||
} else {
|
||||
return dialer.DialContext(ctx, network, address)
|
||||
|
|
|
@ -59,7 +59,7 @@ func SetNetListenConfig(lc *net.ListenConfig) {
|
|||
}
|
||||
|
||||
func TCPKeepAlive(c net.Conn) {
|
||||
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil {
|
||||
if tcp, ok := c.(TCPConn); ok && tcp != nil {
|
||||
tcpKeepAlive(tcp)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,18 @@
|
|||
|
||||
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() {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
|
|
|
@ -4,6 +4,12 @@ package keepalive
|
|||
|
||||
import "net"
|
||||
|
||||
type TCPConn interface {
|
||||
net.Conn
|
||||
SetKeepAlive(keepalive bool) error
|
||||
SetKeepAliveConfig(config net.KeepAliveConfig) error
|
||||
}
|
||||
|
||||
func keepAliveConfig() net.KeepAliveConfig {
|
||||
config := net.KeepAliveConfig{
|
||||
Enable: true,
|
||||
|
@ -18,7 +24,7 @@ func keepAliveConfig() net.KeepAliveConfig {
|
|||
return config
|
||||
}
|
||||
|
||||
func tcpKeepAlive(tcp *net.TCPConn) {
|
||||
func tcpKeepAlive(tcp TCPConn) {
|
||||
if DisableKeepAlive() {
|
||||
_ = tcp.SetKeepAlive(false)
|
||||
} else {
|
||||
|
|
|
@ -87,6 +87,7 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
|
|||
default:
|
||||
continue
|
||||
}
|
||||
srcIP = srcIP.Unmap()
|
||||
|
||||
if ip == srcIP {
|
||||
// xsocket_n.so_last_pid
|
||||
|
|
|
@ -10,7 +10,6 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
)
|
||||
|
||||
|
@ -136,13 +135,14 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
|
|||
switch {
|
||||
case flag&0x1 > 0 && isIPv4:
|
||||
// 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:
|
||||
// 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:
|
||||
continue
|
||||
}
|
||||
srcIP = srcIP.Unmap()
|
||||
|
||||
if ip != srcIP {
|
||||
continue
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
@ -137,7 +136,8 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
|
|||
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
|
||||
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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() {
|
||||
if DefaultResolver != nil {
|
||||
go DefaultResolver.ResetConnection()
|
||||
|
|
|
@ -399,9 +399,7 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
|
|||
}
|
||||
}
|
||||
|
||||
_ = q.tryAssemble()
|
||||
|
||||
return nil
|
||||
return q.tryAssemble()
|
||||
}
|
||||
|
||||
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)) {
|
||||
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)
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
utls "github.com/metacubex/utls"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
|
@ -36,9 +37,9 @@ type RealityConfig struct {
|
|||
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
|
||||
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
|
||||
for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
|
||||
verifier := &realityVerifier{
|
||||
serverName: tlsConfig.ServerName,
|
||||
}
|
||||
|
@ -60,6 +61,27 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
|
|||
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
|
||||
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
|
||||
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"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,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"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-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,
|
||||
ExcludeUID: rawTun.ExcludeUID,
|
||||
ExcludeUIDRange: rawTun.ExcludeUIDRange,
|
||||
ExcludeSrcPort: rawTun.ExcludeSrcPort,
|
||||
ExcludeSrcPortRange: rawTun.ExcludeSrcPortRange,
|
||||
ExcludeDstPort: rawTun.ExcludeDstPort,
|
||||
ExcludeDstPortRange: rawTun.ExcludeDstPortRange,
|
||||
IncludeAndroidUser: rawTun.IncludeAndroidUser,
|
||||
IncludePackage: rawTun.IncludePackage,
|
||||
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(metadata *Metadata, touch bool) Proxy
|
||||
|
||||
// Close releasing associated resources
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Group interface {
|
||||
|
|
|
@ -6,11 +6,15 @@ import (
|
|||
"net"
|
||||
"net/netip"
|
||||
"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 (
|
||||
TCP NetWork = iota
|
||||
UDP
|
||||
|
@ -37,6 +41,21 @@ const (
|
|||
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
|
||||
|
||||
func (n NetWork) String() string {
|
||||
|
@ -207,14 +226,14 @@ func (m *Metadata) SourceValid() bool {
|
|||
return m.SrcPort != 0 && m.SrcIP.IsValid()
|
||||
}
|
||||
|
||||
func (m *Metadata) AddrType() int {
|
||||
func (m *Metadata) AddrType() AddrType {
|
||||
switch true {
|
||||
case m.Host != "" || !m.DstIP.IsValid():
|
||||
return socks5.AtypDomainName
|
||||
return AtypDomainName
|
||||
case m.DstIP.Is4():
|
||||
return socks5.AtypIPv4
|
||||
return AtypIPv4
|
||||
default:
|
||||
return socks5.AtypIPv6
|
||||
return AtypIPv6
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/lru"
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/component/fakeip"
|
||||
R "github.com/metacubex/mihomo/component/resolver"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
@ -120,14 +119,21 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware {
|
|||
|
||||
switch a := ans.(type) {
|
||||
case *D.A:
|
||||
ip = nnip.IpToAddr(a.A)
|
||||
ip, _ = netip.AddrFromSlice(a.A)
|
||||
ttl = a.Hdr.Ttl
|
||||
case *D.AAAA:
|
||||
ip = nnip.IpToAddr(a.AAAA)
|
||||
ip, _ = netip.AddrFromSlice(a.AAAA)
|
||||
ttl = a.Hdr.Ttl
|
||||
default:
|
||||
continue
|
||||
}
|
||||
if !ip.IsValid() {
|
||||
continue
|
||||
}
|
||||
if !ip.IsGlobalUnicast() {
|
||||
continue
|
||||
}
|
||||
ip = ip.Unmap()
|
||||
|
||||
if ttl < 1 {
|
||||
ttl = 1
|
||||
|
|
|
@ -19,6 +19,16 @@ func dnsReadConfig() (servers []string, err error) {
|
|||
return
|
||||
}
|
||||
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 {
|
||||
sa, err := dns.Address.Sockaddr.Sockaddr()
|
||||
if err != nil {
|
||||
|
@ -63,7 +73,8 @@ func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
|
|||
l := uint32(15000) // recommended initial size
|
||||
for {
|
||||
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 l == 0 {
|
||||
return nil, nil
|
||||
|
|
20
dns/util.go
20
dns/util.go
|
@ -10,7 +10,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
"github.com/metacubex/mihomo/common/picker"
|
||||
"github.com/metacubex/mihomo/component/dialer"
|
||||
"github.com/metacubex/mihomo/component/resolver"
|
||||
|
@ -150,19 +149,24 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
|
|||
return msg
|
||||
}
|
||||
|
||||
func msgToIP(msg *D.Msg) []netip.Addr {
|
||||
ips := []netip.Addr{}
|
||||
|
||||
func msgToIP(msg *D.Msg) (ips []netip.Addr) {
|
||||
for _, answer := range msg.Answer {
|
||||
var ip netip.Addr
|
||||
switch ans := answer.(type) {
|
||||
case *D.AAAA:
|
||||
ips = append(ips, nnip.IpToAddr(ans.AAAA))
|
||||
ip, _ = netip.AddrFromSlice(ans.AAAA)
|
||||
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 ips
|
||||
return
|
||||
}
|
||||
|
||||
func msgToDomain(msg *D.Msg) string {
|
||||
|
|
|
@ -1298,6 +1298,31 @@ listeners:
|
|||
# password: "example"
|
||||
### 注意,对于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
|
||||
type: tun
|
||||
# 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/bart v0.19.0
|
||||
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/quic-go v0.49.1-0.20250212162123-c135a4412996
|
||||
github.com/metacubex/randv2 v0.2.0
|
||||
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c
|
||||
github.com/metacubex/sing-shadowsocks v0.2.8
|
||||
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-wireguard v0.0.0-20241126021510-0827d417b589
|
||||
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/miekg/dns v1.1.63
|
||||
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/sing v0.5.2
|
||||
github.com/sagernet/sing-mux v0.2.1
|
||||
github.com/sagernet/sing-shadowtls v0.1.5
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
|
||||
github.com/samber/lo v1.49.1
|
||||
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20
|
||||
|
|
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/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
|
||||
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
|
||||
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c=
|
||||
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
|
||||
github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
|
||||
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/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
|
||||
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/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
|
||||
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/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
|
||||
github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
|
||||
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/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
|
||||
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg=
|
||||
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
|
||||
github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
|
||||
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/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
|
||||
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
|
||||
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI=
|
||||
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk=
|
||||
github.com/metacubex/utls v1.6.8-alpha.6 h1:5ZdZNiZFkKKgEcuPOOROIc8bA4dX2VJHoY3gajSnSaU=
|
||||
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/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
|
||||
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/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
|
||||
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
|
||||
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
|
||||
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
|
||||
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=
|
||||
|
|
|
@ -221,7 +221,7 @@ func updateExperimental(c *config.Experimental) {
|
|||
if c.QUICGoDisableECN {
|
||||
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
|
||||
}
|
||||
dialer.GetIP4PEnable(c.IP4PEnable)
|
||||
resolver.SetIP4PEnable(c.IP4PEnable)
|
||||
}
|
||||
|
||||
func updateNTP(c *config.NTP) {
|
||||
|
|
|
@ -86,6 +86,11 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||
if err != nil {
|
||||
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)
|
||||
|
||||
go func() {
|
||||
|
@ -130,8 +135,6 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
|
|||
|
||||
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
|
||||
ctx := context.TODO()
|
||||
|
||||
conn = tls.Server(conn, l.tlsConfig)
|
||||
defer conn.Close()
|
||||
|
||||
b := buf.NewPacket()
|
||||
|
|
|
@ -3,9 +3,9 @@ package config
|
|||
import (
|
||||
"net/netip"
|
||||
|
||||
"github.com/metacubex/mihomo/common/nnip"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
|
@ -50,6 +50,10 @@ type Tun struct {
|
|||
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
|
||||
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,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"`
|
||||
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
|
||||
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
|
||||
|
@ -66,11 +70,11 @@ type Tun struct {
|
|||
func (t *Tun) Sort() {
|
||||
slices.Sort(t.DNSHijack)
|
||||
|
||||
slices.SortFunc(t.Inet4Address, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet6Address, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.RouteAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet4Address, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.Inet6Address, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.RouteAddress, netipx.ComparePrefix)
|
||||
slices.Sort(t.RouteAddressSet)
|
||||
slices.SortFunc(t.RouteExcludeAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.RouteExcludeAddress, netipx.ComparePrefix)
|
||||
slices.Sort(t.RouteExcludeAddressSet)
|
||||
slices.Sort(t.IncludeInterface)
|
||||
slices.Sort(t.ExcludeInterface)
|
||||
|
@ -82,10 +86,10 @@ func (t *Tun) Sort() {
|
|||
slices.Sort(t.IncludePackage)
|
||||
slices.Sort(t.ExcludePackage)
|
||||
|
||||
slices.SortFunc(t.Inet4RouteAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet6RouteAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet4RouteExcludeAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet6RouteExcludeAddress, nnip.PrefixCompare)
|
||||
slices.SortFunc(t.Inet4RouteAddress, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.Inet6RouteAddress, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.Inet4RouteExcludeAddress, netipx.ComparePrefix)
|
||||
slices.SortFunc(t.Inet6RouteExcludeAddress, netipx.ComparePrefix)
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
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"`
|
||||
ExcludeUID []uint32 `inbound:"exclude-uid,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"`
|
||||
IncludePackage []string `inbound:"include-package,omitempty"`
|
||||
ExcludePackage []string `inbound:"exclude-package,omitempty"`
|
||||
|
@ -137,6 +141,10 @@ func NewTun(options *TunOption) (*Tun, error) {
|
|||
IncludeUIDRange: options.IncludeUIDRange,
|
||||
ExcludeUID: options.ExcludeUID,
|
||||
ExcludeUIDRange: options.ExcludeUIDRange,
|
||||
ExcludeSrcPort: options.ExcludeSrcPort,
|
||||
ExcludeSrcPortRange: options.ExcludeSrcPortRange,
|
||||
ExcludeDstPort: options.ExcludeDstPort,
|
||||
ExcludeDstPortRange: options.ExcludeDstPortRange,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
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 (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"strconv"
|
||||
|
||||
N "github.com/metacubex/mihomo/common/net"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
@ -16,9 +14,13 @@ func New(t C.Tunnel) {
|
|||
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 {
|
||||
return nil, errors.New("tcp uninitialized")
|
||||
return nil, errors.New("tunnel uninitialized")
|
||||
}
|
||||
// executor Parsed
|
||||
conn1, conn2 := N.Pipe()
|
||||
|
@ -31,15 +33,8 @@ func HandleTcp(address string, proxy string) (conn net.Conn, err error) {
|
|||
if proxy != "" {
|
||||
metadata.SpecialProxy = proxy
|
||||
}
|
||||
if h, port, err := net.SplitHostPort(address); err == nil {
|
||||
if port, err := strconv.ParseUint(port, 10, 16); err == nil {
|
||||
metadata.DstPort = uint16(port)
|
||||
}
|
||||
if ip, err := netip.ParseAddr(h); err == nil {
|
||||
metadata.DstIP = ip
|
||||
} else {
|
||||
metadata.Host = h
|
||||
}
|
||||
if err = metadata.SetRemoteAddress(address); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go tunnel.HandleTCPConn(conn2, metadata)
|
||||
|
|
|
@ -73,7 +73,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,13 +9,15 @@ import (
|
|||
"net"
|
||||
"time"
|
||||
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/listener/inner"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"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 {
|
||||
Dest string
|
||||
|
@ -26,13 +28,14 @@ type Config struct {
|
|||
Proxy string
|
||||
}
|
||||
|
||||
func (c Config) Build() (*Builder, error) {
|
||||
realityConfig := &reality.Config{}
|
||||
func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
|
||||
realityConfig := &utls.RealityConfig{}
|
||||
realityConfig.SessionTicketsDisabled = true
|
||||
realityConfig.Type = "tcp"
|
||||
realityConfig.Dest = c.Dest
|
||||
realityConfig.Time = ntp.Now
|
||||
realityConfig.ServerNames = make(map[string]bool)
|
||||
realityConfig.Log = log.Debugln
|
||||
for _, it := range c.ServerNames {
|
||||
realityConfig.ServerNames[it] = true
|
||||
}
|
||||
|
@ -50,7 +53,11 @@ func (c Config) Build() (*Builder, error) {
|
|||
realityConfig.ShortIds = make(map[[8]byte]bool)
|
||||
for i, shortIDString := range c.ShortID {
|
||||
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 {
|
||||
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) {
|
||||
return inner.HandleTcp(address, c.Proxy)
|
||||
return inner.HandleTcp(tunnel, address, c.Proxy)
|
||||
}
|
||||
|
||||
return &Builder{realityConfig}, nil
|
||||
}
|
||||
|
||||
type Builder struct {
|
||||
realityConfig *reality.Config
|
||||
realityConfig *utls.RealityConfig
|
||||
}
|
||||
|
||||
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.
|
||||
// We fixed it by calling Close() directly.
|
||||
l = realityListenerWrapper{l}
|
||||
|
@ -80,7 +87,7 @@ func (b Builder) NewListener(l net.Listener) net.Listener {
|
|||
}
|
||||
|
||||
type realityConnWrapper struct {
|
||||
*reality.Conn
|
||||
*utls.Conn
|
||||
}
|
||||
|
||||
func (c realityConnWrapper) Upstream() any {
|
||||
|
@ -100,5 +107,5 @@ func (l realityListenerWrapper) Accept() (net.Conn, error) {
|
|||
if err != nil {
|
||||
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 {
|
||||
return ctx
|
||||
},
|
||||
Logger: log.SingLogger,
|
||||
Logger: log.SingInfoToDebugLogger, // convert sing-mux info log to debug
|
||||
Handler: h,
|
||||
Padding: lc.MuxOption.Padding,
|
||||
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...)
|
||||
err := l.service.NewConnection(ctx, conn, M.Metadata{
|
||||
Protocol: "shadowsocks",
|
||||
Source: M.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||
Source: M.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
LC "github.com/metacubex/mihomo/listener/config"
|
||||
"github.com/metacubex/mihomo/listener/sing"
|
||||
"github.com/metacubex/mihomo/log"
|
||||
"golang.org/x/exp/constraints"
|
||||
|
||||
tun "github.com/metacubex/sing-tun"
|
||||
"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")
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
|
@ -339,6 +356,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
|
|||
ExcludeInterface: options.ExcludeInterface,
|
||||
IncludeUID: includeUID,
|
||||
ExcludeUID: excludeUID,
|
||||
ExcludeSrcPort: excludeSrcPort,
|
||||
ExcludeDstPort: excludeDstPort,
|
||||
IncludeAndroidUser: options.IncludeAndroidUser,
|
||||
IncludePackage: options.IncludePackage,
|
||||
ExcludePackage: options.ExcludePackage,
|
||||
|
@ -566,13 +585,13 @@ func (d *cDialerInterfaceFinder) FindInterfaceName(destination netip.Addr) strin
|
|||
return "<invalid>"
|
||||
}
|
||||
|
||||
func uidToRange(uidList []uint32) []ranges.Range[uint32] {
|
||||
return common.Map(uidList, func(uid uint32) ranges.Range[uint32] {
|
||||
func uidToRange[T constraints.Integer](uidList []T) []ranges.Range[T] {
|
||||
return common.Map(uidList, func(uid T) ranges.Range[T] {
|
||||
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 {
|
||||
if !strings.Contains(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 {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
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...)
|
||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||
Protocol: "vless",
|
||||
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
|
|
|
@ -90,7 +90,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
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...)
|
||||
err := l.service.NewConnection(ctx, conn, metadata.Metadata{
|
||||
Protocol: "vmess",
|
||||
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()),
|
||||
Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
|
||||
})
|
||||
if err != nil {
|
||||
_ = conn.Close()
|
||||
|
|
|
@ -72,7 +72,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
|
|||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
|
|||
}
|
||||
|
||||
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() {
|
||||
// try to unmap 4in6 address
|
||||
|
|
|
@ -84,7 +84,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
|
|||
if tlsConfig.Certificates != nil {
|
||||
return nil, errors.New("certificate is unavailable in reality")
|
||||
}
|
||||
realityBuilder, err = config.RealityConfig.Build()
|
||||
realityBuilder, err = config.RealityConfig.Build(tunnel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -60,6 +60,14 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
|
|||
} else {
|
||||
tlsConfig.NextProtos = []string{"h3"}
|
||||
}
|
||||
|
||||
if config.MaxIdleTime == 0 {
|
||||
config.MaxIdleTime = 15000
|
||||
}
|
||||
if config.AuthenticationTimeout == 0 {
|
||||
config.AuthenticationTimeout = 1000
|
||||
}
|
||||
|
||||
quicConfig := &quic.Config{
|
||||
MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond,
|
||||
MaxIncomingStreams: ServerMaxIncomingStreams,
|
||||
|
|
13
log/sing.go
13
log/sing.go
|
@ -65,4 +65,17 @@ func (l singLogger) Panic(args ...any) {
|
|||
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 SingInfoToDebugLogger L.ContextLogger = singInfoToDebugLogger{}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/metacubex/mihomo/common/atomic"
|
||||
"github.com/metacubex/mihomo/common/buf"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
"github.com/metacubex/mihomo/transport/anytls/padding"
|
||||
"github.com/metacubex/mihomo/transport/anytls/session"
|
||||
"github.com/metacubex/mihomo/transport/vmess"
|
||||
|
@ -83,12 +82,7 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
|
|||
b.WriteZeroN(paddingLen)
|
||||
}
|
||||
|
||||
getTlsConn := func() (net.Conn, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
|
||||
defer cancel()
|
||||
return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
|
||||
}
|
||||
tlsConn, err := getTlsConn()
|
||||
tlsConn, err := vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, err
|
||||
|
|
|
@ -40,7 +40,7 @@ func newStream(id uint32, sess *Session) *Stream {
|
|||
// Read implements net.Conn
|
||||
func (s *Stream) Read(b []byte) (n int, err error) {
|
||||
n, err = s.pipeR.Read(b)
|
||||
if s.dieErr != nil {
|
||||
if n == 0 && s.dieErr != nil {
|
||||
err = s.dieErr
|
||||
}
|
||||
return
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/url"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -21,6 +22,7 @@ import (
|
|||
"github.com/metacubex/mihomo/common/buf"
|
||||
"github.com/metacubex/mihomo/common/pool"
|
||||
tlsC "github.com/metacubex/mihomo/component/tls"
|
||||
C "github.com/metacubex/mihomo/constant"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
@ -35,17 +37,17 @@ var defaultHeader = http.Header{
|
|||
"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 {
|
||||
initFn func() (io.ReadCloser, error)
|
||||
writer io.Writer
|
||||
flusher http.Flusher
|
||||
initFn func() (io.ReadCloser, netAddr, error)
|
||||
writer io.Writer
|
||||
closer io.Closer
|
||||
netAddr
|
||||
|
||||
reader io.ReadCloser
|
||||
once sync.Once
|
||||
close atomic.Bool
|
||||
closed atomic.Bool
|
||||
err error
|
||||
remain int
|
||||
br *bufio.Reader
|
||||
|
@ -60,7 +62,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
func (g *Conn) initReader() {
|
||||
reader, err := g.initFn()
|
||||
reader, addr, err := g.initFn()
|
||||
if err != nil {
|
||||
g.err = err
|
||||
if closer, ok := g.writer.(io.Closer); ok {
|
||||
|
@ -68,8 +70,9 @@ func (g *Conn) initReader() {
|
|||
}
|
||||
return
|
||||
}
|
||||
g.netAddr = addr
|
||||
|
||||
if !g.close.Load() {
|
||||
if !g.closed.Load() {
|
||||
g.reader = reader
|
||||
g.br = bufio.NewReader(reader)
|
||||
} else {
|
||||
|
@ -147,8 +150,8 @@ func (g *Conn) Write(b []byte) (n int, err error) {
|
|||
err = g.err
|
||||
}
|
||||
|
||||
if g.flusher != nil {
|
||||
g.flusher.Flush()
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
return len(b), err
|
||||
|
@ -170,8 +173,8 @@ func (g *Conn) WriteBuffer(buffer *buf.Buffer) error {
|
|||
err = g.err
|
||||
}
|
||||
|
||||
if g.flusher != nil {
|
||||
g.flusher.Flush()
|
||||
if flusher, ok := g.writer.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
return err
|
||||
|
@ -182,15 +185,28 @@ func (g *Conn) FrontHeadroom() int {
|
|||
}
|
||||
|
||||
func (g *Conn) Close() error {
|
||||
g.close.Store(true)
|
||||
g.closed.Store(true)
|
||||
var errorArr []error
|
||||
|
||||
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 {
|
||||
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) }
|
||||
|
@ -208,24 +224,26 @@ func (g *Conn) SetDeadline(t time.Time) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
||||
wrap := TransportWrap{}
|
||||
|
||||
func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, clientFingerprint string, realityConfig *tlsC.RealityConfig) *TransportWrap {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
wrap.remoteAddr = pconn.RemoteAddr()
|
||||
wrap.localAddr = pconn.LocalAddr()
|
||||
|
||||
if tlsConfig == 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 fingerprint, exists := tlsC.GetFingerprint(Fingerprint); exists {
|
||||
if fingerprint, exists := tlsC.GetFingerprint(clientFingerprint); exists {
|
||||
utlsConn := tlsC.UClient(pconn, cfg, fingerprint)
|
||||
if err := utlsConn.HandshakeContext(ctx); err != nil {
|
||||
pconn.Close()
|
||||
|
@ -239,7 +257,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
|||
return utlsConn, nil
|
||||
}
|
||||
} else {
|
||||
realityConn, err := tlsC.GetRealityConn(ctx, pconn, Fingerprint, cfg, realityConfig)
|
||||
realityConn, err := tlsC.GetRealityConn(ctx, pconn, clientFingerprint, cfg, realityConfig)
|
||||
if err != nil {
|
||||
pconn.Close()
|
||||
return nil, err
|
||||
|
@ -269,7 +287,7 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
|||
return conn, nil
|
||||
}
|
||||
|
||||
wrap.Transport = &http2.Transport{
|
||||
transport := &http2.Transport{
|
||||
DialTLSContext: dialFunc,
|
||||
TLSClientConfig: tlsConfig,
|
||||
AllowHTTP: false,
|
||||
|
@ -277,7 +295,13 @@ func NewHTTP2Client(dialFn DialFn, tlsConfig *tls.Config, Fingerprint string, re
|
|||
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) {
|
||||
|
@ -302,17 +326,25 @@ func StreamGunWithTransport(transport *TransportWrap, cfg *Config) (net.Conn, er
|
|||
ProtoMinor: 0,
|
||||
Header: defaultHeader,
|
||||
}
|
||||
request = request.WithContext(transport.ctx)
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nAddr, err
|
||||
}
|
||||
return response.Body, nil
|
||||
return response.Body, nAddr, nil
|
||||
},
|
||||
writer: writer,
|
||||
netAddr: transport.netAddr,
|
||||
writer: writer,
|
||||
}
|
||||
|
||||
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) {
|
||||
dialFn := func(network, addr string) (net.Conn, error) {
|
||||
dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
conn := &Conn{
|
||||
initFn: func() (io.ReadCloser, error) {
|
||||
return request.Body, nil
|
||||
initFn: func() (io.ReadCloser, netAddr, error) {
|
||||
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,
|
||||
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
|
||||
writer: writer,
|
||||
}
|
||||
_ = conn.Init()
|
||||
|
||||
wrapper := &h2ConnWrapper{
|
||||
// gun.Conn can't correct handle ReadDeadline
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
package gun
|
||||
|
||||
import (
|
||||
"golang.org/x/net/http2"
|
||||
"context"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type TransportWrap struct {
|
||||
*http2.Transport
|
||||
netAddr
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
closeOnce sync.Once
|
||||
}
|
||||
|
||||
func (tw *TransportWrap) RemoteAddr() net.Addr {
|
||||
return tw.remoteAddr
|
||||
}
|
||||
|
||||
func (tw *TransportWrap) LocalAddr() net.Addr {
|
||||
return tw.localAddr
|
||||
func (tw *TransportWrap) Close() error {
|
||||
tw.closeOnce.Do(func() {
|
||||
tw.cancel()
|
||||
closeTransport(tw.Transport)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
type netAddr struct {
|
||||
|
@ -23,10 +28,10 @@ type netAddr struct {
|
|||
localAddr net.Addr
|
||||
}
|
||||
|
||||
func (addr *netAddr) RemoteAddr() net.Addr {
|
||||
func (addr netAddr) RemoteAddr() net.Addr {
|
||||
return addr.remoteAddr
|
||||
}
|
||||
|
||||
func (addr *netAddr) LocalAddr() net.Addr {
|
||||
func (addr netAddr) LocalAddr() net.Addr {
|
||||
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