Update On Fri Apr 25 14:33:06 CEST 2025

This commit is contained in:
github-action[bot]
2025-04-25 14:33:07 +02:00
parent 2ac24642d2
commit 05a86235e7
465 changed files with 8693 additions and 4758 deletions

1
.github/update.log vendored
View File

@@ -978,3 +978,4 @@ Update On Wed Apr 16 20:40:48 CEST 2025
Update On Thu Apr 17 20:38:25 CEST 2025 Update On Thu Apr 17 20:38:25 CEST 2025
Update On Fri Apr 18 20:35:44 CEST 2025 Update On Fri Apr 18 20:35:44 CEST 2025
Update On Sat Apr 19 20:35:13 CEST 2025 Update On Sat Apr 19 20:35:13 CEST 2025
Update On Fri Apr 25 14:32:58 CEST 2025

View File

@@ -46,8 +46,8 @@ subprojects {
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionName = "2.11.8" versionName = "2.11.9"
versionCode = 211008 versionCode = 211009
resValue("string", "release_name", "v$versionName") resValue("string", "release_name", "v$versionName")
resValue("integer", "release_code", "$versionCode") resValue("integer", "release_code", "$versionCode")

View File

@@ -14,7 +14,7 @@ on:
- Alpha - Alpha
tags: tags:
- "v*" - "v*"
pull_request_target: pull_request:
branches: branches:
- Alpha - Alpha
concurrency: concurrency:

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

View File

@@ -290,6 +290,7 @@ func (p *Proxy) URLTest(ctx context.Context, url string, expectedStatus utils.In
t = uint16(time.Since(start) / time.Millisecond) t = uint16(time.Since(start) / time.Millisecond)
return return
} }
func NewProxy(adapter C.ProxyAdapter) *Proxy { func NewProxy(adapter C.ProxyAdapter) *Proxy {
return &Proxy{ return &Proxy{
ProxyAdapter: adapter, ProxyAdapter: adapter,

View File

@@ -7,7 +7,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/metacubex/mihomo/common/nnip"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
@@ -21,13 +20,13 @@ func parseSocksAddr(target socks5.Addr) *C.Metadata {
metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".") metadata.Host = strings.TrimRight(string(target[2:2+target[1]]), ".")
metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1])) metadata.DstPort = uint16((int(target[2+target[1]]) << 8) | int(target[2+target[1]+1]))
case socks5.AtypIPv4: case socks5.AtypIPv4:
metadata.DstIP = nnip.IpToAddr(net.IP(target[1 : 1+net.IPv4len])) metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv4len])
metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1])) metadata.DstPort = uint16((int(target[1+net.IPv4len]) << 8) | int(target[1+net.IPv4len+1]))
case socks5.AtypIPv6: case socks5.AtypIPv6:
ip6, _ := netip.AddrFromSlice(target[1 : 1+net.IPv6len]) metadata.DstIP, _ = netip.AddrFromSlice(target[1 : 1+net.IPv6len])
metadata.DstIP = ip6.Unmap()
metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1])) metadata.DstPort = uint16((int(target[1+net.IPv6len]) << 8) | int(target[1+net.IPv6len+1]))
} }
metadata.DstIP = metadata.DstIP.Unmap()
return metadata return metadata
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"net" "net"
"runtime"
"strconv" "strconv"
"time" "time"
@@ -12,7 +11,6 @@ import (
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls" "github.com/metacubex/mihomo/transport/anytls"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
@@ -52,7 +50,7 @@ func (t *AnyTLS) DialContext(ctx context.Context, metadata *C.Metadata, opts ...
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewConn(CN.NewRefConn(c, t), t), nil return NewConn(c, t), nil
} }
func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
@@ -73,7 +71,7 @@ func (t *AnyTLS) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
metadata.DstIP = ip metadata.DstIP = ip
} }
destination := M.SocksaddrFromNet(metadata.UDPAddr()) destination := M.SocksaddrFromNet(metadata.UDPAddr())
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), t), nil return newPacketConn(CN.NewThreadSafePacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination})), t), nil
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
@@ -88,6 +86,11 @@ func (t *AnyTLS) ProxyInfo() C.ProxyInfo {
return info return info
} }
// Close implements C.ProxyAdapter
func (t *AnyTLS) Close() error {
return t.client.Close()
}
func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) { func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
@@ -111,9 +114,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
if tlsConfig.Host == "" { if tlsConfig.Host == "" {
tlsConfig.Host = option.Server tlsConfig.Host = option.Server
} }
if tlsC.HaveGlobalFingerprint() && len(option.ClientFingerprint) == 0 {
tlsConfig.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
tOption.TLSConfig = tlsConfig tOption.TLSConfig = tlsConfig
outbound := &AnyTLS{ outbound := &AnyTLS{
@@ -132,9 +132,6 @@ func NewAnyTLS(option AnyTLSOption) (*AnyTLS, error) {
option: &option, option: &option,
dialer: singDialer, dialer: singDialer,
} }
runtime.SetFinalizer(outbound, func(o *AnyTLS) {
_ = o.client.Close()
})
return outbound, nil return outbound, nil
} }

View File

@@ -4,15 +4,23 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"net" "net"
"runtime"
"strings" "strings"
"sync"
"syscall" "syscall"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/log"
) )
type ProxyAdapter interface {
C.ProxyAdapter
DialOptions(opts ...dialer.Option) []dialer.Option
}
type Base struct { type Base struct {
name string name string
addr string addr string
@@ -152,6 +160,10 @@ func (b *Base) DialOptions(opts ...dialer.Option) []dialer.Option {
return opts return opts
} }
func (b *Base) Close() error {
return nil
}
type BasicOption struct { type BasicOption struct {
TFO bool `proxy:"tfo,omitempty"` TFO bool `proxy:"tfo,omitempty"`
MPTCP bool `proxy:"mptcp,omitempty"` MPTCP bool `proxy:"mptcp,omitempty"`
@@ -221,6 +233,10 @@ func (c *conn) ReaderReplaceable() bool {
return true return true
} }
func (c *conn) AddRef(ref any) {
c.ExtendedConn = N.NewRefConn(c.ExtendedConn, ref) // add ref for autoCloseProxyAdapter
}
func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn { func NewConn(c net.Conn, a C.ProxyAdapter) C.Conn {
if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn if _, ok := c.(syscall.Conn); !ok { // exclusion system conn like *net.TCPConn
c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly c = N.NewDeadlineConn(c) // most conn from outbound can't handle readDeadline correctly
@@ -267,6 +283,10 @@ func (c *packetConn) ReaderReplaceable() bool {
return true return true
} }
func (c *packetConn) AddRef(ref any) {
c.EnhancePacketConn = N.NewRefPacketConn(c.EnhancePacketConn, ref) // add ref for autoCloseProxyAdapter
}
func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn { func newPacketConn(pc net.PacketConn, a C.ProxyAdapter) C.PacketConn {
epc := N.NewEnhancePacketConn(pc) epc := N.NewEnhancePacketConn(pc)
if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn if _, ok := pc.(syscall.Conn); !ok { // exclusion system conn like *net.UDPConn
@@ -286,3 +306,75 @@ func parseRemoteDestination(addr string) string {
} }
} }
} }
type AddRef interface {
AddRef(ref any)
}
type autoCloseProxyAdapter struct {
ProxyAdapter
closeOnce sync.Once
closeErr error
}
func (p *autoCloseProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
c, err := p.ProxyAdapter.DialContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
if c, ok := c.(AddRef); ok {
c.AddRef(p)
}
return c, nil
}
func (p *autoCloseProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.Conn, err error) {
c, err := p.ProxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if c, ok := c.(AddRef); ok {
c.AddRef(p)
}
return c, nil
}
func (p *autoCloseProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
if err != nil {
return nil, err
}
if pc, ok := pc.(AddRef); ok {
pc.AddRef(p)
}
return pc, nil
}
func (p *autoCloseProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc, err := p.ProxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
if err != nil {
return nil, err
}
if pc, ok := pc.(AddRef); ok {
pc.AddRef(p)
}
return pc, nil
}
func (p *autoCloseProxyAdapter) Close() error {
p.closeOnce.Do(func() {
log.Debugln("Closing outdated proxy [%s]", p.Name())
runtime.SetFinalizer(p, nil)
p.closeErr = p.ProxyAdapter.Close()
})
return p.closeErr
}
func NewAutoCloseProxyAdapter(adapter ProxyAdapter) ProxyAdapter {
proxy := &autoCloseProxyAdapter{
ProxyAdapter: adapter,
}
// auto close ProxyAdapter
runtime.SetFinalizer(proxy, (*autoCloseProxyAdapter).Close)
return proxy
}

View File

@@ -7,11 +7,11 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -51,7 +51,7 @@ func (h *Http) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Me
} }
} }
if err := h.shakeHand(metadata, c); err != nil { if err := h.shakeHandContext(ctx, c, metadata); err != nil {
return nil, err return nil, err
} }
return c, nil return c, nil
@@ -99,7 +99,12 @@ func (h *Http) ProxyInfo() C.ProxyInfo {
return info return info
} }
func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { func (h *Http) shakeHandContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
addr := metadata.RemoteAddress() addr := metadata.RemoteAddress()
HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n" HeaderString := "CONNECT " + addr + " HTTP/1.1\r\n"
tempHeaders := map[string]string{ tempHeaders := map[string]string{
@@ -123,13 +128,13 @@ func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
HeaderString += "\r\n" HeaderString += "\r\n"
_, err := rw.Write([]byte(HeaderString)) _, err = c.Write([]byte(HeaderString))
if err != nil { if err != nil {
return err return err
} }
resp, err := http.ReadResponse(bufio.NewReader(rw), nil) resp, err := http.ReadResponse(bufio.NewReader(c), nil)
if err != nil { if err != nil {
return err return err

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"time" "time"
@@ -15,7 +14,6 @@ import (
"github.com/metacubex/quic-go/congestion" "github.com/metacubex/quic-go/congestion"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -45,8 +43,6 @@ type Hysteria struct {
option *HysteriaOption option *HysteriaOption
client *core.Client client *core.Client
closeCh chan struct{} // for test
} }
func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) { func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
@@ -55,7 +51,7 @@ func (h *Hysteria) DialContext(ctx context.Context, metadata *C.Metadata, opts .
return nil, err return nil, err
} }
return NewConn(CN.NewRefConn(tcpConn, h), h), nil return NewConn(tcpConn, h), nil
} }
func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) { func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
@@ -63,7 +59,7 @@ func (h *Hysteria) ListenPacketContext(ctx context.Context, metadata *C.Metadata
if err != nil { if err != nil {
return nil, err return nil, err
} }
return newPacketConn(CN.NewRefPacketConn(&hyPacketConn{udpConn}, h), h), nil return newPacketConn(&hyPacketConn{udpConn}, h), nil
} }
func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer { func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.PacketDialer {
@@ -82,7 +78,7 @@ func (h *Hysteria) genHdc(ctx context.Context, opts ...dialer.Option) utils.Pack
return cDialer.ListenPacket(ctx, network, "", rAddrPort) return cDialer.ListenPacket(ctx, network, "", rAddrPort)
}, },
remoteAddr: func(addr string) (net.Addr, error) { remoteAddr: func(addr string) (net.Addr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, h.prefer) return resolveUDPAddr(ctx, "udp", addr, h.prefer)
}, },
} }
} }
@@ -239,18 +235,16 @@ func NewHysteria(option HysteriaOption) (*Hysteria, error) {
option: &option, option: &option,
client: client, client: client,
} }
runtime.SetFinalizer(outbound, closeHysteria)
return outbound, nil return outbound, nil
} }
func closeHysteria(h *Hysteria) { // Close implements C.ProxyAdapter
func (h *Hysteria) Close() error {
if h.client != nil { if h.client != nil {
_ = h.client.Close() return h.client.Close()
}
if h.closeCh != nil {
close(h.closeCh)
} }
return nil
} }
type hyPacketConn struct { type hyPacketConn struct {

View File

@@ -6,7 +6,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"runtime"
"strconv" "strconv"
"time" "time"
@@ -39,8 +38,6 @@ type Hysteria2 struct {
option *Hysteria2Option option *Hysteria2Option
client *hysteria2.Client client *hysteria2.Client
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
closeCh chan struct{} // for test
} }
type Hysteria2Option struct { type Hysteria2Option struct {
@@ -78,7 +75,7 @@ func (h *Hysteria2) DialContext(ctx context.Context, metadata *C.Metadata, opts
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewConn(CN.NewRefConn(c, h), h), nil return NewConn(c, h), nil
} }
func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
@@ -91,16 +88,15 @@ func (h *Hysteria2) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if pc == nil { if pc == nil {
return nil, errors.New("packetConn is nil") return nil, errors.New("packetConn is nil")
} }
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), h), h), nil return newPacketConn(CN.NewThreadSafePacketConn(pc), h), nil
} }
func closeHysteria2(h *Hysteria2) { // Close implements C.ProxyAdapter
func (h *Hysteria2) Close() error {
if h.client != nil { if h.client != nil {
_ = h.client.CloseWithError(errors.New("proxy removed")) return h.client.CloseWithError(errors.New("proxy removed"))
}
if h.closeCh != nil {
close(h.closeCh)
} }
return nil
} }
// ProxyInfo implements C.ProxyAdapter // ProxyInfo implements C.ProxyAdapter
@@ -175,7 +171,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
CWND: option.CWND, CWND: option.CWND,
UdpMTU: option.UdpMTU, UdpMTU: option.UdpMTU,
ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) { ServerAddress: func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion)) return resolveUDPAddr(ctx, "udp", addr, C.NewDNSPrefer(option.IPVersion))
}, },
} }
@@ -192,7 +188,7 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
}) })
if len(serverAddress) > 0 { if len(serverAddress) > 0 {
clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) { clientOptions.ServerAddress = func(ctx context.Context) (*net.UDPAddr, error) {
return resolveUDPAddrWithPrefer(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion)) return resolveUDPAddr(ctx, "udp", serverAddress[randv2.IntN(len(serverAddress))], C.NewDNSPrefer(option.IPVersion))
} }
if option.HopInterval == 0 { if option.HopInterval == 0 {
@@ -226,7 +222,6 @@ func NewHysteria2(option Hysteria2Option) (*Hysteria2, error) {
client: client, client: client,
dialer: singDialer, dialer: singDialer,
} }
runtime.SetFinalizer(outbound, closeHysteria2)
return outbound, nil return outbound, nil
} }

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"runtime"
"strconv" "strconv"
"sync" "sync"
@@ -62,7 +61,7 @@ func (m *Mieru) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
if err != nil { if err != nil {
return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err) return nil, fmt.Errorf("dial to %s failed: %w", metadata.UDPAddr(), err)
} }
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), m), nil return newPacketConn(CN.NewThreadSafePacketConn(mierucommon.NewUDPAssociateWrapper(mierucommon.NewPacketOverStreamTunnel(c))), m), nil
} }
// SupportUOT implements C.ProxyAdapter // SupportUOT implements C.ProxyAdapter
@@ -141,16 +140,17 @@ func NewMieru(option MieruOption) (*Mieru, error) {
option: &option, option: &option,
client: c, client: c,
} }
runtime.SetFinalizer(outbound, closeMieru)
return outbound, nil return outbound, nil
} }
func closeMieru(m *Mieru) { // Close implements C.ProxyAdapter
func (m *Mieru) Close() error {
m.mu.Lock() m.mu.Lock()
defer m.mu.Unlock() defer m.mu.Unlock()
if m.client != nil && m.client.IsRunning() { if m.client != nil && m.client.IsRunning() {
m.client.Stop() return m.client.Stop()
} }
return nil
} }
func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec { func metadataToMieruNetAddrSpec(metadata *C.Metadata) mierumodel.NetAddrSpec {

View File

@@ -20,16 +20,19 @@ func (o RealityOptions) Parse() (*tlsC.RealityConfig, error) {
config := new(tlsC.RealityConfig) config := new(tlsC.RealityConfig)
const x25519ScalarSize = 32 const x25519ScalarSize = 32
var publicKey [x25519ScalarSize]byte publicKey, err := base64.RawURLEncoding.DecodeString(o.PublicKey)
n, err := base64.RawURLEncoding.Decode(publicKey[:], []byte(o.PublicKey)) if err != nil || len(publicKey) != x25519ScalarSize {
if err != nil || n != x25519ScalarSize {
return nil, errors.New("invalid REALITY public key") return nil, errors.New("invalid REALITY public key")
} }
config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey[:]) config.PublicKey, err = ecdh.X25519().NewPublicKey(publicKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("fail to create REALITY public key: %w", err) return nil, fmt.Errorf("fail to create REALITY public key: %w", err)
} }
n := hex.DecodedLen(len(o.ShortID))
if n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short id")
}
n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID)) n, err = hex.Decode(config.ShortID[:], []byte(o.ShortID))
if err != nil || n > tlsC.RealityMaxShortIDLen { if err != nil || n > tlsC.RealityMaxShortIDLen {
return nil, errors.New("invalid REALITY short ID") return nil, errors.New("invalid REALITY short ID")

View File

@@ -19,7 +19,6 @@ import (
shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls" shadowtls "github.com/metacubex/mihomo/transport/sing-shadowtls"
v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin" v2rayObfs "github.com/metacubex/mihomo/transport/v2ray-plugin"
restlsC "github.com/3andne/restls-client-go"
shadowsocks "github.com/metacubex/sing-shadowsocks2" shadowsocks "github.com/metacubex/sing-shadowsocks2"
"github.com/sagernet/sing/common/bufio" "github.com/sagernet/sing/common/bufio"
M "github.com/sagernet/sing/common/metadata" M "github.com/sagernet/sing/common/metadata"
@@ -37,7 +36,7 @@ type ShadowSocks struct {
v2rayOption *v2rayObfs.Option v2rayOption *v2rayObfs.Option
gostOption *gost.Option gostOption *gost.Option
shadowTLSOption *shadowtls.ShadowTLSOption shadowTLSOption *shadowtls.ShadowTLSOption
restlsConfig *restlsC.Config restlsConfig *restls.Config
} }
type ShadowSocksOption struct { type ShadowSocksOption struct {
@@ -100,7 +99,7 @@ type restlsOption struct {
} }
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
useEarly := false useEarly := false
switch ss.obfsMode { switch ss.obfsMode {
case "tls": case "tls":
@@ -109,7 +108,6 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
_, port, _ := net.SplitHostPort(ss.addr) _, port, _ := net.SplitHostPort(ss.addr)
c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port) c = obfs.NewHTTPObfs(c, ss.obfsOption.Host, port)
case "websocket": case "websocket":
var err error
if ss.v2rayOption != nil { if ss.v2rayOption != nil {
c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption) c, err = v2rayObfs.NewV2rayObfs(ctx, c, ss.v2rayOption)
} else if ss.gostOption != nil { } else if ss.gostOption != nil {
@@ -121,14 +119,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
return nil, fmt.Errorf("%s connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s connect error: %w", ss.addr, err)
} }
case shadowtls.Mode: case shadowtls.Mode:
var err error
c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption) c, err = shadowtls.NewShadowTLS(ctx, c, ss.shadowTLSOption)
if err != nil { if err != nil {
return nil, err return nil, err
} }
useEarly = true useEarly = true
case restls.Mode: case restls.Mode:
var err error
c, err = restls.NewRestls(ctx, c, ss.restlsConfig) c, err = restls.NewRestls(ctx, c, ss.restlsConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err) return nil, fmt.Errorf("%s (restls) connect error: %w", ss.addr, err)
@@ -136,6 +132,12 @@ func (ss *ShadowSocks) StreamConnContext(ctx context.Context, c net.Conn, metada
useEarly = true useEarly = true
} }
useEarly = useEarly || N.NeedHandshake(c) useEarly = useEarly || N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
}
if metadata.NetWork == C.UDP && ss.option.UDPOverTCP { if metadata.NetWork == C.UDP && ss.option.UDPOverTCP {
uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion)) uotDestination := uot.RequestDestination(uint8(ss.option.UDPOverTCPVersion))
if useEarly { if useEarly {
@@ -197,7 +199,7 @@ func (ss *ShadowSocks) ListenPacketWithDialer(ctx context.Context, dialer C.Dial
return nil, err return nil, err
} }
} }
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ss.addr, ss.prefer) addr, err := resolveUDPAddr(ctx, "udp", ss.addr, ss.prefer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -262,7 +264,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
var gostOption *gost.Option var gostOption *gost.Option
var obfsOption *simpleObfsOption var obfsOption *simpleObfsOption
var shadowTLSOpt *shadowtls.ShadowTLSOption var shadowTLSOpt *shadowtls.ShadowTLSOption
var restlsConfig *restlsC.Config var restlsConfig *restls.Config
obfsMode := "" obfsMode := ""
decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true}) decoder := structure.NewDecoder(structure.Option{TagName: "obfs", WeaklyTypedInput: true})
@@ -347,7 +349,7 @@ func NewShadowSocks(option ShadowSocksOption) (*ShadowSocks, error) {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }
restlsConfig, err = restlsC.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint) restlsConfig, err = restls.NewRestlsConfig(restlsOpt.Host, restlsOpt.Password, restlsOpt.VersionHint, restlsOpt.RestlsScript, option.ClientFingerprint)
if err != nil { if err != nil {
return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err) return nil, fmt.Errorf("ss %s initialize restls-plugin error: %w", addr, err)
} }

View File

@@ -42,12 +42,15 @@ type ShadowSocksROption struct {
} }
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (ssr *ShadowSocksR) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
c = ssr.obfs.StreamConn(c) c = ssr.obfs.StreamConn(c)
c = ssr.cipher.StreamConn(c) c = ssr.cipher.StreamConn(c)
var ( var (
iv []byte iv []byte
err error
) )
switch conn := c.(type) { switch conn := c.(type) {
case *shadowstream.Conn: case *shadowstream.Conn:
@@ -102,7 +105,7 @@ func (ssr *ShadowSocksR) ListenPacketWithDialer(ctx context.Context, dialer C.Di
return nil, err return nil, err
} }
} }
addr, err := resolveUDPAddrWithPrefer(ctx, "udp", ssr.addr, ssr.prefer) addr, err := resolveUDPAddr(ctx, "udp", ssr.addr, ssr.prefer)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -3,7 +3,6 @@ package outbound
import ( import (
"context" "context"
"errors" "errors"
"runtime"
CN "github.com/metacubex/mihomo/common/net" CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
@@ -18,8 +17,7 @@ import (
) )
type SingMux struct { type SingMux struct {
C.ProxyAdapter ProxyAdapter
base ProxyBase
client *mux.Client client *mux.Client
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
onlyTcp bool onlyTcp bool
@@ -43,25 +41,21 @@ type BrutalOption struct {
Down string `proxy:"down,omitempty"` Down string `proxy:"down,omitempty"`
} }
type ProxyBase interface {
DialOptions(opts ...dialer.Option) []dialer.Option
}
func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (s *SingMux) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
options := s.base.DialOptions(opts...) options := s.ProxyAdapter.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...)) s.dialer.SetDialer(dialer.NewDialer(options...))
c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) c, err := s.client.DialContext(ctx, "tcp", M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return NewConn(CN.NewRefConn(c, s), s.ProxyAdapter), err return NewConn(c, s), err
} }
func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
if s.onlyTcp { if s.onlyTcp {
return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...) return s.ProxyAdapter.ListenPacketContext(ctx, metadata, opts...)
} }
options := s.base.DialOptions(opts...) options := s.ProxyAdapter.DialOptions(opts...)
s.dialer.SetDialer(dialer.NewDialer(options...)) s.dialer.SetDialer(dialer.NewDialer(options...))
// sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr // sing-mux use stream-oriented udp with a special address, so we need a net.UDPAddr
@@ -80,7 +74,7 @@ func (s *SingMux) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
if pc == nil { if pc == nil {
return nil, E.New("packetConn is nil") return nil, E.New("packetConn is nil")
} }
return newPacketConn(CN.NewRefPacketConn(CN.NewThreadSafePacketConn(pc), s), s.ProxyAdapter), nil return newPacketConn(CN.NewThreadSafePacketConn(pc), s), nil
} }
func (s *SingMux) SupportUDP() bool { func (s *SingMux) SupportUDP() bool {
@@ -103,11 +97,15 @@ func (s *SingMux) ProxyInfo() C.ProxyInfo {
return info return info
} }
func closeSingMux(s *SingMux) { // Close implements C.ProxyAdapter
func (s *SingMux) Close() error {
if s.client != nil {
_ = s.client.Close() _ = s.client.Close()
} }
return s.ProxyAdapter.Close()
}
func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.ProxyAdapter, error) { func NewSingMux(option SingMuxOption, proxy ProxyAdapter) (ProxyAdapter, error) {
// TODO // TODO
// "TCP Brutal is only supported on Linux-based systems" // "TCP Brutal is only supported on Linux-based systems"
@@ -131,11 +129,9 @@ func NewSingMux(option SingMuxOption, proxy C.ProxyAdapter, base ProxyBase) (C.P
} }
outbound := &SingMux{ outbound := &SingMux{
ProxyAdapter: proxy, ProxyAdapter: proxy,
base: base,
client: client, client: client,
dialer: singDialer, dialer: singDialer,
onlyTcp: option.OnlyTcp, onlyTcp: option.OnlyTcp,
} }
runtime.SetFinalizer(outbound, closeSingMux)
return outbound, nil return outbound, nil
} }

View File

@@ -6,6 +6,7 @@ import (
"net" "net"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -41,7 +42,7 @@ type streamOption struct {
obfsOption *simpleObfsOption obfsOption *simpleObfsOption
} }
func streamConn(c net.Conn, option streamOption) *snell.Snell { func snellStreamConn(c net.Conn, option streamOption) *snell.Snell {
switch option.obfsOption.Mode { switch option.obfsOption.Mode {
case "tls": case "tls":
c = obfs.NewTLSObfs(c, option.obfsOption.Host) c = obfs.NewTLSObfs(c, option.obfsOption.Host)
@@ -54,25 +55,35 @@ func streamConn(c net.Conn, option streamOption) *snell.Snell {
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (s *Snell) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption}) c = snellStreamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
if metadata.NetWork == C.UDP { err := s.writeHeaderContext(ctx, c, metadata)
err := snell.WriteUDPHeader(c, s.version)
return c, err return c, err
} }
err := snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
return c, err func (s *Snell) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
if metadata.NetWork == C.UDP {
err = snell.WriteUDPHeader(c, s.version)
return
}
err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version)
return
} }
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (s *Snell) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
if s.version == snell.Version2 && len(opts) == 0 { if s.version == snell.Version2 && dialer.IsZeroOptions(opts) {
c, err := s.pool.Get() c, err := s.pool.Get()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = snell.WriteHeader(c, metadata.String(), uint(metadata.DstPort), s.version); err != nil { if err = s.writeHeaderContext(ctx, c, metadata); err != nil {
c.Close() _ = c.Close()
return nil, err return nil, err
} }
return NewConn(c, s), err return NewConn(c, s), err
@@ -120,12 +131,8 @@ func (s *Snell) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, met
if err != nil { if err != nil {
return nil, err return nil, err
} }
c = streamConn(c, streamOption{s.psk, s.version, s.addr, s.obfsOption})
err = snell.WriteUDPHeader(c, s.version) c, err = s.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, err
}
pc := snell.PacketConn(c) pc := snell.PacketConn(c)
return newPacketConn(pc, s), nil return newPacketConn(pc, s), nil
@@ -212,7 +219,7 @@ func NewSnell(option SnellOption) (*Snell, error) {
return nil, err return nil, err
} }
return streamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil return snellStreamConn(c, streamOption{psk, option.Version, addr, obfsOption}), nil
}) })
} }
return s, nil return s, nil

View File

@@ -10,6 +10,7 @@ import (
"net/netip" "net/netip"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -58,7 +59,7 @@ func (ss *Socks5) StreamConnContext(ctx context.Context, c net.Conn, metadata *C
Password: ss.pass, Password: ss.pass,
} }
} }
if _, err := socks5.ClientHandshake(c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil { if _, err := ss.clientHandshakeContext(ctx, c, serializesSocksAddr(metadata), socks5.CmdConnect, user); err != nil {
return nil, err return nil, err
} }
return c, nil return c, nil
@@ -135,7 +136,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
} }
udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0)) udpAssocateAddr := socks5.AddrFromStdAddrPort(netip.AddrPortFrom(netip.IPv4Unspecified(), 0))
bindAddr, err := socks5.ClientHandshake(c, udpAssocateAddr, socks5.CmdUDPAssociate, user) bindAddr, err := ss.clientHandshakeContext(ctx, c, udpAssocateAddr, socks5.CmdUDPAssociate, user)
if err != nil { if err != nil {
err = fmt.Errorf("client hanshake error: %w", err) err = fmt.Errorf("client hanshake error: %w", err)
return return
@@ -147,7 +148,7 @@ func (ss *Socks5) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
err = errors.New("invalid UDP bind address") err = errors.New("invalid UDP bind address")
return return
} else if bindUDPAddr.IP.IsUnspecified() { } else if bindUDPAddr.IP.IsUnspecified() {
serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr()) serverAddr, err := resolveUDPAddr(ctx, "udp", ss.Addr(), C.IPv4Prefer)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -178,6 +179,14 @@ func (ss *Socks5) ProxyInfo() C.ProxyInfo {
return info return info
} }
func (ss *Socks5) clientHandshakeContext(ctx context.Context, c net.Conn, addr socks5.Addr, command socks5.Command, user *socks5.User) (_ socks5.Addr, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
return socks5.ClientHandshake(c, addr, command, user)
}
func NewSocks5(option Socks5Option) (*Socks5, error) { func NewSocks5(option Socks5Option) (*Socks5, error) {
var tlsConfig *tls.Config var tlsConfig *tls.Config
if option.TLS { if option.TLS {

View File

@@ -7,7 +7,6 @@ import (
"fmt" "fmt"
"net" "net"
"os" "os"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@@ -25,7 +24,10 @@ type Ssh struct {
*Base *Base
option *SshOption option *SshOption
client *sshClient // using a standalone struct to avoid its inner loop invalidate the Finalizer
config *ssh.ClientConfig
client *ssh.Client
cMutex sync.Mutex
} }
type SshOption struct { type SshOption struct {
@@ -49,7 +51,7 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
return nil, err return nil, err
} }
} }
client, err := s.client.connect(ctx, cDialer, s.addr) client, err := s.connect(ctx, cDialer, s.addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -58,16 +60,10 @@ func (s *Ssh) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dia
return nil, err return nil, err
} }
return NewConn(N.NewRefConn(c, s), s), nil return NewConn(c, s), nil
} }
type sshClient struct { func (s *Ssh) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
config *ssh.ClientConfig
client *ssh.Client
cMutex sync.Mutex
}
func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string) (client *ssh.Client, err error) {
s.cMutex.Lock() s.cMutex.Lock()
defer s.cMutex.Unlock() defer s.cMutex.Unlock()
if s.client != nil { if s.client != nil {
@@ -108,7 +104,15 @@ func (s *sshClient) connect(ctx context.Context, cDialer C.Dialer, addr string)
return client, nil return client, nil
} }
func (s *sshClient) Close() error { // ProxyInfo implements C.ProxyAdapter
func (s *Ssh) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
// Close implements C.ProxyAdapter
func (s *Ssh) Close() error {
s.cMutex.Lock() s.cMutex.Lock()
defer s.cMutex.Unlock() defer s.cMutex.Unlock()
if s.client != nil { if s.client != nil {
@@ -117,17 +121,6 @@ func (s *sshClient) Close() error {
return nil return nil
} }
func closeSsh(s *Ssh) {
_ = s.client.Close()
}
// ProxyInfo implements C.ProxyAdapter
func (s *Ssh) ProxyInfo() C.ProxyInfo {
info := s.Base.ProxyInfo()
info.DialerProxy = s.option.DialerProxy
return info
}
func NewSsh(option SshOption) (*Ssh, error) { func NewSsh(option SshOption) (*Ssh, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
@@ -204,11 +197,8 @@ func NewSsh(option SshOption) (*Ssh, error) {
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
option: &option, option: &option,
client: &sshClient{
config: &config, config: &config,
},
} }
runtime.SetFinalizer(outbound, closeSsh)
return outbound, nil return outbound, nil
} }

View File

@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
N "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/ca" "github.com/metacubex/mihomo/component/ca"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
@@ -17,12 +18,13 @@ import (
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/shadowsocks/core" "github.com/metacubex/mihomo/transport/shadowsocks/core"
"github.com/metacubex/mihomo/transport/trojan" "github.com/metacubex/mihomo/transport/trojan"
"github.com/metacubex/mihomo/transport/vmess"
) )
type Trojan struct { type Trojan struct {
*Base *Base
instance *trojan.Trojan
option *TrojanOption option *TrojanOption
hexPassword [trojan.KeyLength]byte
// for gun mux // for gun mux
gunTLSConfig *tls.Config gunTLSConfig *tls.Config
@@ -60,15 +62,21 @@ type TrojanSSOption struct {
Password string `proxy:"password,omitempty"` Password string `proxy:"password,omitempty"`
} }
func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error) { // StreamConnContext implements C.ProxyAdapter
if t.option.Network == "ws" { func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
switch t.option.Network {
case "ws":
host, port, _ := net.SplitHostPort(t.addr) host, port, _ := net.SplitHostPort(t.addr)
wsOpts := &trojan.WebsocketOption{
wsOpts := &vmess.WebsocketConfig{
Host: host, Host: host,
Port: port, Port: port,
Path: t.option.WSOpts.Path, Path: t.option.WSOpts.Path,
MaxEarlyData: t.option.WSOpts.MaxEarlyData,
EarlyDataHeaderName: t.option.WSOpts.EarlyDataHeaderName,
V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade, V2rayHttpUpgrade: t.option.WSOpts.V2rayHttpUpgrade,
V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen, V2rayHttpUpgradeFastOpen: t.option.WSOpts.V2rayHttpUpgradeFastOpen,
ClientFingerprint: t.option.ClientFingerprint,
Headers: http.Header{}, Headers: http.Header{},
} }
@@ -82,57 +90,95 @@ func (t *Trojan) plainStream(ctx context.Context, c net.Conn) (net.Conn, error)
} }
} }
return t.instance.StreamWebsocketConn(ctx, c, wsOpts) alpn := trojan.DefaultWebsocketALPN
if len(t.option.ALPN) != 0 {
alpn = t.option.ALPN
} }
return t.instance.StreamConn(ctx, c) wsOpts.TLS = true
tlsConfig := &tls.Config{
NextProtos: alpn,
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: t.option.SkipCertVerify,
ServerName: t.option.SNI,
} }
// StreamConnContext implements C.ProxyAdapter wsOpts.TLSConfig, err = ca.GetSpecifiedFingerprintTLSConfig(tlsConfig, t.option.Fingerprint)
func (t *Trojan) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(t.option.ClientFingerprint) == 0 {
t.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
if t.transport != nil {
c, err = gun.StreamGunWithConn(c, t.gunTLSConfig, t.gunConfig, t.realityConfig)
} else {
c, err = t.plainStream(ctx, c)
}
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
if metadata.NetWork == C.UDP {
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
return c, err
}
err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata))
return c, err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
// gun transport
if t.transport != nil && len(opts) == 0 {
c, err := gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
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)
}
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 { if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c) c = t.ssCipher.StreamConn(c)
} }
if err = t.instance.WriteHeader(c, trojan.CommandTCP, serializesSocksAddr(metadata)); err != nil { if ctx.Done() != nil {
c.Close() done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
command := trojan.CommandTCP
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return c, err
}
func (t *Trojan) writeHeaderContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
command := trojan.CommandTCP
if metadata.NetWork == C.UDP {
command = trojan.CommandUDP
}
err = trojan.WriteHeader(c, t.hexPassword, command, serializesSocksAddr(metadata))
return err
}
// DialContext implements C.ProxyAdapter
func (t *Trojan) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
var c net.Conn
// gun transport
if t.transport != nil && dialer.IsZeroOptions(opts) {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil {
return nil, err
}
defer func(c net.Conn) {
safeConnClose(c, err)
}(c)
c, err = t.streamConnContext(ctx, c, metadata)
if err != nil {
return nil, err return nil, err
} }
@@ -171,7 +217,7 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
var c net.Conn var c net.Conn
// grpc transport // grpc transport
if t.transport != nil && len(opts) == 0 { if t.transport != nil && dialer.IsZeroOptions(opts) {
c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig) c, err = gun.StreamGunWithTransport(t.transport, t.gunConfig)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err) return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
@@ -180,16 +226,12 @@ func (t *Trojan) ListenPacketContext(ctx context.Context, metadata *C.Metadata,
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
if t.ssCipher != nil { c, err = t.streamConnContext(ctx, c, metadata)
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil { if err != nil {
return nil, err return nil, err
} }
pc := t.instance.PacketConn(c) pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata) return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.Base.DialOptions(opts...)...), metadata)
@@ -210,21 +252,12 @@ func (t *Trojan) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, me
defer func(c net.Conn) { defer func(c net.Conn) {
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = t.plainStream(ctx, c) c, err = t.StreamConnContext(ctx, c, metadata)
if err != nil {
return nil, fmt.Errorf("%s connect error: %w", t.addr, err)
}
if t.ssCipher != nil {
c = t.ssCipher.StreamConn(c)
}
err = t.instance.WriteHeader(c, trojan.CommandUDP, serializesSocksAddr(metadata))
if err != nil { if err != nil {
return nil, err return nil, err
} }
pc := t.instance.PacketConn(c) pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
@@ -235,7 +268,7 @@ func (t *Trojan) SupportWithDialer() C.NetWork {
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (t *Trojan) ListenPacketOnStreamConn(c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
pc := t.instance.PacketConn(c) pc := trojan.NewPacketConn(c)
return newPacketConn(pc, t), err return newPacketConn(pc, t), err
} }
@@ -251,22 +284,17 @@ func (t *Trojan) ProxyInfo() C.ProxyInfo {
return info return info
} }
// Close implements C.ProxyAdapter
func (t *Trojan) Close() error {
if t.transport != nil {
return t.transport.Close()
}
return nil
}
func NewTrojan(option TrojanOption) (*Trojan, error) { func NewTrojan(option TrojanOption) (*Trojan, error) {
addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port)) addr := net.JoinHostPort(option.Server, strconv.Itoa(option.Port))
tOption := &trojan.Option{
Password: option.Password,
ALPN: option.ALPN,
ServerName: option.Server,
SkipCertVerify: option.SkipCertVerify,
Fingerprint: option.Fingerprint,
ClientFingerprint: option.ClientFingerprint,
}
if option.SNI != "" {
tOption.ServerName = option.SNI
}
t := &Trojan{ t := &Trojan{
Base: &Base{ Base: &Base{
name: option.Name, name: option.Name,
@@ -279,8 +307,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
rmark: option.RoutingMark, rmark: option.RoutingMark,
prefer: C.NewDNSPrefer(option.IPVersion), prefer: C.NewDNSPrefer(option.IPVersion),
}, },
instance: trojan.New(tOption),
option: &option, option: &option,
hexPassword: trojan.Key(option.Password),
} }
var err error var err error
@@ -288,7 +316,6 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
tOption.Reality = t.realityConfig
if option.SSOpts.Enabled { if option.SSOpts.Enabled {
if option.SSOpts.Password == "" { if option.SSOpts.Password == "" {
@@ -305,7 +332,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
} }
if option.Network == "grpc" { if option.Network == "grpc" {
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error var err error
var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...) var cDialer C.Dialer = dialer.NewDialer(t.Base.DialOptions()...)
if len(t.option.DialerProxy) > 0 { if len(t.option.DialerProxy) > 0 {
@@ -314,7 +341,7 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err return nil, err
} }
} }
c, err := cDialer.DialContext(context.Background(), "tcp", t.addr) c, err := cDialer.DialContext(ctx, "tcp", t.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", t.addr, err.Error())
} }
@@ -324,8 +351,8 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
NextProtos: option.ALPN, NextProtos: option.ALPN,
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
InsecureSkipVerify: tOption.SkipCertVerify, InsecureSkipVerify: option.SkipCertVerify,
ServerName: tOption.ServerName, ServerName: option.SNI,
} }
var err error var err error
@@ -334,12 +361,13 @@ func NewTrojan(option TrojanOption) (*Trojan, error) {
return nil, err return nil, err
} }
t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, tOption.ClientFingerprint, t.realityConfig) t.transport = gun.NewHTTP2Client(dialFn, tlsConfig, option.ClientFingerprint, t.realityConfig)
t.gunTLSConfig = tlsConfig t.gunTLSConfig = tlsConfig
t.gunConfig = &gun.Config{ t.gunConfig = &gun.Config{
ServiceName: option.GrpcOpts.GrpcServiceName, ServiceName: option.GrpcOpts.GrpcServiceName,
Host: tOption.ServerName, Host: option.SNI,
ClientFingerprint: option.ClientFingerprint,
} }
} }

View File

@@ -130,7 +130,7 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
return nil, nil, err return nil, nil, err
} }
} }
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", t.addr, t.prefer) udpAddr, err := resolveUDPAddr(ctx, "udp", t.addr, t.prefer)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

@@ -3,118 +3,59 @@ package outbound
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"regexp" "regexp"
"strconv" "strconv"
"sync"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/socks5" "github.com/metacubex/mihomo/transport/socks5"
) )
var (
globalClientSessionCache tls.ClientSessionCache
once sync.Once
)
func getClientSessionCache() tls.ClientSessionCache {
once.Do(func() {
globalClientSessionCache = tls.NewLRUClientSessionCache(128)
})
return globalClientSessionCache
}
func serializesSocksAddr(metadata *C.Metadata) []byte { func serializesSocksAddr(metadata *C.Metadata) []byte {
var buf [][]byte var buf [][]byte
addrType := metadata.AddrType() addrType := metadata.AddrType()
aType := uint8(addrType)
p := uint(metadata.DstPort) p := uint(metadata.DstPort)
port := []byte{uint8(p >> 8), uint8(p & 0xff)} port := []byte{uint8(p >> 8), uint8(p & 0xff)}
switch addrType { switch addrType {
case socks5.AtypDomainName: case C.AtypDomainName:
lenM := uint8(len(metadata.Host)) lenM := uint8(len(metadata.Host))
host := []byte(metadata.Host) host := []byte(metadata.Host)
buf = [][]byte{{aType, lenM}, host, port} buf = [][]byte{{socks5.AtypDomainName, lenM}, host, port}
case socks5.AtypIPv4: case C.AtypIPv4:
host := metadata.DstIP.AsSlice() host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port} buf = [][]byte{{socks5.AtypIPv4}, host, port}
case socks5.AtypIPv6: case C.AtypIPv6:
host := metadata.DstIP.AsSlice() host := metadata.DstIP.AsSlice()
buf = [][]byte{{aType}, host, port} buf = [][]byte{{socks5.AtypIPv6}, host, port}
} }
return bytes.Join(buf, nil) return bytes.Join(buf, nil)
} }
func resolveUDPAddr(ctx context.Context, network, address string) (*net.UDPAddr, error) { func resolveUDPAddr(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
ip, err := resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err != nil {
return nil, err
}
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
}
func resolveUDPAddrWithPrefer(ctx context.Context, network, address string, prefer C.DNSPrefer) (*net.UDPAddr, error) {
host, port, err := net.SplitHostPort(address) host, port, err := net.SplitHostPort(address)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var ip netip.Addr var ip netip.Addr
var fallback netip.Addr
switch prefer { switch prefer {
case C.IPv4Only: case C.IPv4Only:
ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver) ip, err = resolver.ResolveIPv4WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Only: case C.IPv6Only:
ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver) ip, err = resolver.ResolveIPv6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
case C.IPv6Prefer: case C.IPv6Prefer:
var ips []netip.Addr ip, err = resolver.ResolveIPPrefer6WithResolver(ctx, host, resolver.ProxyServerHostResolver)
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err == nil {
for _, addr := range ips {
if addr.Is6() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
default: default:
// C.IPv4Prefer, C.DualStack and other ip, err = resolver.ResolveIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
var ips []netip.Addr
ips, err = resolver.LookupIPWithResolver(ctx, host, resolver.ProxyServerHostResolver)
if err == nil {
for _, addr := range ips {
if addr.Is4() {
ip = addr
break
} else {
if !fallback.IsValid() {
fallback = addr
}
}
}
}
}
if !ip.IsValid() && fallback.IsValid() {
ip = fallback
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
ip, port = resolver.LookupIP4P(ip, port)
return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port)) return net.ResolveUDPAddr(network, net.JoinHostPort(ip.String(), port))
} }

View File

@@ -22,7 +22,6 @@ import (
tlsC "github.com/metacubex/mihomo/component/tls" tlsC "github.com/metacubex/mihomo/component/tls"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/gun" "github.com/metacubex/mihomo/transport/gun"
"github.com/metacubex/mihomo/transport/socks5"
"github.com/metacubex/mihomo/transport/vless" "github.com/metacubex/mihomo/transport/vless"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
@@ -76,13 +75,7 @@ type VlessOption struct {
ClientFingerprint string `proxy:"client-fingerprint,omitempty"` ClientFingerprint string `proxy:"client-fingerprint,omitempty"`
} }
func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
var err error
if tlsC.HaveGlobalFingerprint() && len(v.option.ClientFingerprint) == 0 {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
@@ -156,7 +149,7 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.HTTP2Opts.Path, Path: v.option.HTTP2Opts.Path,
} }
c, err = vmess.StreamH2Conn(c, h2Opts) c, err = vmess.StreamH2Conn(ctx, c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default: default:
@@ -169,10 +162,14 @@ func (v *Vless) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
return nil, err return nil, err
} }
return v.streamConn(c, metadata) return v.streamConnContext(ctx, c, metadata)
} }
func (v *Vless) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) { func (v *Vless) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
if v.option.PacketAddr { if v.option.PacketAddr {
metadata = &C.Metadata{ metadata = &C.Metadata{
@@ -229,9 +226,10 @@ func (v *Vless) streamTLSConn(ctx context.Context, conn net.Conn, isH2 bool) (ne
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && dialer.IsZeroOptions(opts) {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig) c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -239,7 +237,7 @@ func (v *Vless) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.client.StreamConn(c, parseVlessAddr(metadata, v.option.XUDP)) c, err = v.streamConnContext(ctx, c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -284,7 +282,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
var c net.Conn var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && dialer.IsZeroOptions(opts) {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -293,7 +291,7 @@ func (v *Vless) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.streamConn(c, metadata) c, err = v.streamConnContext(ctx, c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vless client error: %v", err) return nil, fmt.Errorf("new vless client error: %v", err)
} }
@@ -385,19 +383,27 @@ func (v *Vless) ProxyInfo() C.ProxyInfo {
return info return info
} }
// Close implements C.ProxyAdapter
func (v *Vless) Close() error {
if v.transport != nil {
return v.transport.Close()
}
return nil
}
func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr { func parseVlessAddr(metadata *C.Metadata, xudp bool) *vless.DstAddr {
var addrType byte var addrType byte
var addr []byte var addr []byte
switch metadata.AddrType() { switch metadata.AddrType() {
case socks5.AtypIPv4: case C.AtypIPv4:
addrType = vless.AtypIPv4 addrType = vless.AtypIPv4
addr = make([]byte, net.IPv4len) addr = make([]byte, net.IPv4len)
copy(addr[:], metadata.DstIP.AsSlice()) copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypIPv6: case C.AtypIPv6:
addrType = vless.AtypIPv6 addrType = vless.AtypIPv6
addr = make([]byte, net.IPv6len) addr = make([]byte, net.IPv6len)
copy(addr[:], metadata.DstIP.AsSlice()) copy(addr[:], metadata.DstIP.AsSlice())
case socks5.AtypDomainName: case C.AtypDomainName:
addrType = vless.AtypDomainName addrType = vless.AtypDomainName
addr = make([]byte, len(metadata.Host)+1) addr = make([]byte, len(metadata.Host)+1)
addr[0] = byte(len(metadata.Host)) addr[0] = byte(len(metadata.Host))
@@ -563,7 +569,7 @@ func NewVless(option VlessOption) (*Vless, error) {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com") option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
} }
case "grpc": case "grpc":
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...) var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 { if len(v.option.DialerProxy) > 0 {
@@ -572,7 +578,7 @@ func NewVless(option VlessOption) (*Vless, error) {
return nil, err return nil, err
} }
} }
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr) c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -589,10 +595,13 @@ func NewVless(option VlessOption) (*Vless, error) {
} }
var tlsConfig *tls.Config var tlsConfig *tls.Config
if option.TLS { if option.TLS {
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName, ServerName: v.option.ServerName,
}) }, v.option.Fingerprint)
if err != nil {
return nil, err
}
if option.ServerName == "" { if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host tlsConfig.ServerName = host

View File

@@ -96,13 +96,7 @@ type WSOptions struct {
} }
// StreamConnContext implements C.ProxyAdapter // StreamConnContext implements C.ProxyAdapter
func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) { func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ net.Conn, err error) {
var err error
if tlsC.HaveGlobalFingerprint() && (len(v.option.ClientFingerprint) == 0) {
v.option.ClientFingerprint = tlsC.GetGlobalFingerprint()
}
switch v.option.Network { switch v.option.Network {
case "ws": case "ws":
host, port, _ := net.SplitHostPort(v.addr) host, port, _ := net.SplitHostPort(v.addr)
@@ -199,7 +193,7 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
Path: v.option.HTTP2Opts.Path, Path: v.option.HTTP2Opts.Path,
} }
c, err = mihomoVMess.StreamH2Conn(c, h2Opts) c, err = mihomoVMess.StreamH2Conn(ctx, c, h2Opts)
case "grpc": case "grpc":
c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig) c, err = gun.StreamGunWithConn(c, v.gunTLSConfig, v.gunConfig, v.realityConfig)
default: default:
@@ -226,17 +220,24 @@ func (v *Vmess) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.M
if err != nil { if err != nil {
return nil, err return nil, err
} }
return v.streamConn(c, metadata) return v.streamConnContext(ctx, c, metadata)
} }
func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) { func (v *Vmess) streamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (conn net.Conn, err error) {
useEarly := N.NeedHandshake(c)
if !useEarly {
if ctx.Done() != nil {
done := N.SetupContextForConn(ctx, c)
defer done(&err)
}
}
if metadata.NetWork == C.UDP { if metadata.NetWork == C.UDP {
if v.option.XUDP { if v.option.XUDP {
var globalID [8]byte var globalID [8]byte
if metadata.SourceValid() { if metadata.SourceValid() {
globalID = utils.GlobalID(metadata.SourceAddress()) globalID = utils.GlobalID(metadata.SourceAddress())
} }
if N.NeedHandshake(c) { if useEarly {
conn = v.client.DialEarlyXUDPPacketConn(c, conn = v.client.DialEarlyXUDPPacketConn(c,
globalID, globalID,
M.SocksaddrFromNet(metadata.UDPAddr())) M.SocksaddrFromNet(metadata.UDPAddr()))
@@ -246,7 +247,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
M.SocksaddrFromNet(metadata.UDPAddr())) M.SocksaddrFromNet(metadata.UDPAddr()))
} }
} else if v.option.PacketAddr { } else if v.option.PacketAddr {
if N.NeedHandshake(c) { if useEarly {
conn = v.client.DialEarlyPacketConn(c, conn = v.client.DialEarlyPacketConn(c,
M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443)) M.ParseSocksaddrHostPort(packetaddr.SeqPacketMagicAddress, 443))
} else { } else {
@@ -255,7 +256,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
} }
conn = packetaddr.NewBindConn(conn) conn = packetaddr.NewBindConn(conn)
} else { } else {
if N.NeedHandshake(c) { if useEarly {
conn = v.client.DialEarlyPacketConn(c, conn = v.client.DialEarlyPacketConn(c,
M.SocksaddrFromNet(metadata.UDPAddr())) M.SocksaddrFromNet(metadata.UDPAddr()))
} else { } else {
@@ -264,7 +265,7 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
} }
} }
} else { } else {
if N.NeedHandshake(c) { if useEarly {
conn = v.client.DialEarlyConn(c, conn = v.client.DialEarlyConn(c,
M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort))
} else { } else {
@@ -280,9 +281,10 @@ func (v *Vmess) streamConn(c net.Conn, metadata *C.Metadata) (conn net.Conn, err
// DialContext implements C.ProxyAdapter // DialContext implements C.ProxyAdapter
func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && dialer.IsZeroOptions(opts) {
c, err := gun.StreamGunWithTransport(v.transport, v.gunConfig) c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -290,7 +292,7 @@ func (v *Vmess) DialContext(ctx context.Context, metadata *C.Metadata, opts ...d
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.client.DialConn(c, M.ParseSocksaddrHostPort(metadata.String(), metadata.DstPort)) c, err = v.streamConnContext(ctx, c, metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -332,7 +334,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
} }
var c net.Conn var c net.Conn
// gun transport // gun transport
if v.transport != nil && len(opts) == 0 { if v.transport != nil && dialer.IsZeroOptions(opts) {
c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig) c, err = gun.StreamGunWithTransport(v.transport, v.gunConfig)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -341,7 +343,7 @@ func (v *Vmess) ListenPacketContext(ctx context.Context, metadata *C.Metadata, o
safeConnClose(c, err) safeConnClose(c, err)
}(c) }(c)
c, err = v.streamConn(c, metadata) c, err = v.streamConnContext(ctx, c, metadata)
if err != nil { if err != nil {
return nil, fmt.Errorf("new vmess client error: %v", err) return nil, fmt.Errorf("new vmess client error: %v", err)
} }
@@ -395,6 +397,14 @@ func (v *Vmess) ProxyInfo() C.ProxyInfo {
return info return info
} }
// Close implements C.ProxyAdapter
func (v *Vmess) Close() error {
if v.transport != nil {
return v.transport.Close()
}
return nil
}
// ListenPacketOnStreamConn implements C.ProxyAdapter // ListenPacketOnStreamConn implements C.ProxyAdapter
func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) { func (v *Vmess) ListenPacketOnStreamConn(ctx context.Context, c net.Conn, metadata *C.Metadata) (_ C.PacketConn, err error) {
// vmess use stream-oriented udp with a special address, so we need a net.UDPAddr // vmess use stream-oriented udp with a special address, so we need a net.UDPAddr
@@ -470,7 +480,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com") option.HTTP2Opts.Host = append(option.HTTP2Opts.Host, "www.example.com")
} }
case "grpc": case "grpc":
dialFn := func(network, addr string) (net.Conn, error) { dialFn := func(ctx context.Context, network, addr string) (net.Conn, error) {
var err error var err error
var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...) var cDialer C.Dialer = dialer.NewDialer(v.Base.DialOptions()...)
if len(v.option.DialerProxy) > 0 { if len(v.option.DialerProxy) > 0 {
@@ -479,7 +489,7 @@ func NewVmess(option VmessOption) (*Vmess, error) {
return nil, err return nil, err
} }
} }
c, err := cDialer.DialContext(context.Background(), "tcp", v.addr) c, err := cDialer.DialContext(ctx, "tcp", v.addr)
if err != nil { if err != nil {
return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error()) return nil, fmt.Errorf("%s connect error: %s", v.addr, err.Error())
} }
@@ -496,10 +506,13 @@ func NewVmess(option VmessOption) (*Vmess, error) {
} }
var tlsConfig *tls.Config var tlsConfig *tls.Config
if option.TLS { if option.TLS {
tlsConfig = ca.GetGlobalTLSConfig(&tls.Config{ tlsConfig, err = ca.GetSpecifiedFingerprintTLSConfig(&tls.Config{
InsecureSkipVerify: v.option.SkipCertVerify, InsecureSkipVerify: v.option.SkipCertVerify,
ServerName: v.option.ServerName, ServerName: v.option.ServerName,
}) }, v.option.Fingerprint)
if err != nil {
return nil, err
}
if option.ServerName == "" { if option.ServerName == "" {
host, _, _ := net.SplitHostPort(v.addr) host, _, _ := net.SplitHostPort(v.addr)
tlsConfig.ServerName = host tlsConfig.ServerName = host

View File

@@ -8,14 +8,12 @@ import (
"fmt" "fmt"
"net" "net"
"net/netip" "net/netip"
"runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
CN "github.com/metacubex/mihomo/common/net"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/proxydialer" "github.com/metacubex/mihomo/component/proxydialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -45,7 +43,6 @@ type WireGuard struct {
tunDevice wireguard.Device tunDevice wireguard.Device
dialer proxydialer.SingDialer dialer proxydialer.SingDialer
resolver resolver.Resolver resolver resolver.Resolver
refP *refProxyAdapter
initOk atomic.Bool initOk atomic.Bool
initMutex sync.Mutex initMutex sync.Mutex
@@ -57,8 +54,6 @@ type WireGuard struct {
serverAddrMap map[M.Socksaddr]netip.AddrPort serverAddrMap map[M.Socksaddr]netip.AddrPort
serverAddrTime atomic.TypedValue[time.Time] serverAddrTime atomic.TypedValue[time.Time]
serverAddrMutex sync.Mutex serverAddrMutex sync.Mutex
closeCh chan struct{} // for test
} }
type WireGuardOption struct { type WireGuardOption struct {
@@ -173,7 +168,6 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
}, },
dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()), dialer: proxydialer.NewSlowDownSingDialer(proxydialer.NewByNameSingDialer(option.DialerProxy, dialer.NewDialer()), slowdown.New()),
} }
runtime.SetFinalizer(outbound, closeWireGuard)
var reserved [3]uint8 var reserved [3]uint8
if len(option.Reserved) > 0 { if len(option.Reserved) > 0 {
@@ -286,15 +280,13 @@ func NewWireGuard(option WireGuardOption) (*WireGuard, error) {
} }
} }
refP := &refProxyAdapter{}
outbound.refP = refP
if option.RemoteDnsResolve && len(option.Dns) > 0 { if option.RemoteDnsResolve && len(option.Dns) > 0 {
nss, err := dns.ParseNameServer(option.Dns) nss, err := dns.ParseNameServer(option.Dns)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for i := range nss { for i := range nss {
nss[i].ProxyAdapter = refP nss[i].ProxyAdapter = outbound
} }
outbound.resolver = dns.NewResolver(dns.Config{ outbound.resolver = dns.NewResolver(dns.Config{
Main: nss, Main: nss,
@@ -309,7 +301,7 @@ func (w *WireGuard) resolve(ctx context.Context, address M.Socksaddr) (netip.Add
if address.Addr.IsValid() { if address.Addr.IsValid() {
return address.AddrPort(), nil return address.AddrPort(), nil
} }
udpAddr, err := resolveUDPAddrWithPrefer(ctx, "udp", address.String(), w.prefer) udpAddr, err := resolveUDPAddr(ctx, "udp", address.String(), w.prefer)
if err != nil { if err != nil {
return netip.AddrPort{}, err return netip.AddrPort{}, err
} }
@@ -488,13 +480,12 @@ func (w *WireGuard) genIpcConf(ctx context.Context, updateOnly bool) (string, er
return ipcConf, nil return ipcConf, nil
} }
func closeWireGuard(w *WireGuard) { // Close implements C.ProxyAdapter
func (w *WireGuard) Close() error {
if w.device != nil { if w.device != nil {
w.device.Close() w.device.Close()
} }
if w.closeCh != nil { return nil
close(w.closeCh)
}
} }
func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) {
@@ -507,8 +498,6 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if !metadata.Resolved() || w.resolver != nil { if !metadata.Resolved() || w.resolver != nil {
r := resolver.DefaultResolver r := resolver.DefaultResolver
if w.resolver != nil { if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver r = w.resolver
} }
options = append(options, dialer.WithResolver(r)) options = append(options, dialer.WithResolver(r))
@@ -523,7 +512,7 @@ func (w *WireGuard) DialContext(ctx context.Context, metadata *C.Metadata, opts
if conn == nil { if conn == nil {
return nil, E.New("conn is nil") return nil, E.New("conn is nil")
} }
return NewConn(CN.NewRefConn(conn, w), w), nil return NewConn(conn, w), nil
} }
func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) { func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.PacketConn, err error) {
@@ -536,8 +525,6 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" { if (!metadata.Resolved() || w.resolver != nil) && metadata.Host != "" {
r := resolver.DefaultResolver r := resolver.DefaultResolver
if w.resolver != nil { if w.resolver != nil {
w.refP.SetProxyAdapter(w)
defer w.refP.ClearProxyAdapter()
r = w.resolver r = w.resolver
} }
ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r) ip, err := resolver.ResolveIPWithResolver(ctx, metadata.Host, r)
@@ -553,139 +540,10 @@ func (w *WireGuard) ListenPacketContext(ctx context.Context, metadata *C.Metadat
if pc == nil { if pc == nil {
return nil, E.New("packetConn is nil") return nil, E.New("packetConn is nil")
} }
return newPacketConn(CN.NewRefPacketConn(pc, w), w), nil return newPacketConn(pc, w), nil
} }
// IsL3Protocol implements C.ProxyAdapter // IsL3Protocol implements C.ProxyAdapter
func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool { func (w *WireGuard) IsL3Protocol(metadata *C.Metadata) bool {
return true return true
} }
type refProxyAdapter struct {
proxyAdapter C.ProxyAdapter
count int
mutex sync.Mutex
}
func (r *refProxyAdapter) SetProxyAdapter(proxyAdapter C.ProxyAdapter) {
r.mutex.Lock()
defer r.mutex.Unlock()
r.proxyAdapter = proxyAdapter
r.count++
}
func (r *refProxyAdapter) ClearProxyAdapter() {
r.mutex.Lock()
defer r.mutex.Unlock()
r.count--
if r.count == 0 {
r.proxyAdapter = nil
}
}
func (r *refProxyAdapter) Name() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Name()
}
return ""
}
func (r *refProxyAdapter) Type() C.AdapterType {
if r.proxyAdapter != nil {
return r.proxyAdapter.Type()
}
return C.AdapterType(0)
}
func (r *refProxyAdapter) Addr() string {
if r.proxyAdapter != nil {
return r.proxyAdapter.Addr()
}
return ""
}
func (r *refProxyAdapter) SupportUDP() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUDP()
}
return false
}
func (r *refProxyAdapter) ProxyInfo() C.ProxyInfo {
if r.proxyAdapter != nil {
return r.proxyAdapter.ProxyInfo()
}
return C.ProxyInfo{}
}
func (r *refProxyAdapter) MarshalJSON() ([]byte, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.MarshalJSON()
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) StreamConnContext(ctx context.Context, c net.Conn, metadata *C.Metadata) (net.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.StreamConnContext(ctx, c, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketContext(ctx, metadata, opts...)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) SupportUOT() bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportUOT()
}
return false
}
func (r *refProxyAdapter) SupportWithDialer() C.NetWork {
if r.proxyAdapter != nil {
return r.proxyAdapter.SupportWithDialer()
}
return C.InvalidNet
}
func (r *refProxyAdapter) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.DialContextWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.PacketConn, error) {
if r.proxyAdapter != nil {
return r.proxyAdapter.ListenPacketWithDialer(ctx, dialer, metadata)
}
return nil, C.ErrNotSupport
}
func (r *refProxyAdapter) IsL3Protocol(metadata *C.Metadata) bool {
if r.proxyAdapter != nil {
return r.proxyAdapter.IsL3Protocol(metadata)
}
return false
}
func (r *refProxyAdapter) Unwrap(metadata *C.Metadata, touch bool) C.Proxy {
if r.proxyAdapter != nil {
return r.proxyAdapter.Unwrap(metadata, touch)
}
return nil
}
var _ C.ProxyAdapter = (*refProxyAdapter)(nil)

View File

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

View File

@@ -3,8 +3,6 @@ package adapter
import ( import (
"fmt" "fmt"
tlsC "github.com/metacubex/mihomo/component/tls"
"github.com/metacubex/mihomo/adapter/outbound" "github.com/metacubex/mihomo/adapter/outbound"
"github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/structure"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -18,12 +16,12 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
var ( var (
proxy C.ProxyAdapter proxy outbound.ProxyAdapter
err error err error
) )
switch proxyType { switch proxyType {
case "ss": case "ss":
ssOption := &outbound.ShadowSocksOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} ssOption := &outbound.ShadowSocksOption{}
err = decoder.Decode(mapping, ssOption) err = decoder.Decode(mapping, ssOption)
if err != nil { if err != nil {
break break
@@ -56,7 +54,6 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
Method: "GET", Method: "GET",
Path: []string{"/"}, Path: []string{"/"},
}, },
ClientFingerprint: tlsC.GetGlobalFingerprint(),
} }
err = decoder.Decode(mapping, vmessOption) err = decoder.Decode(mapping, vmessOption)
@@ -65,7 +62,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
proxy, err = outbound.NewVmess(*vmessOption) proxy, err = outbound.NewVmess(*vmessOption)
case "vless": case "vless":
vlessOption := &outbound.VlessOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} vlessOption := &outbound.VlessOption{}
err = decoder.Decode(mapping, vlessOption) err = decoder.Decode(mapping, vlessOption)
if err != nil { if err != nil {
break break
@@ -79,7 +76,7 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
} }
proxy, err = outbound.NewSnell(*snellOption) proxy, err = outbound.NewSnell(*snellOption)
case "trojan": case "trojan":
trojanOption := &outbound.TrojanOption{ClientFingerprint: tlsC.GetGlobalFingerprint()} trojanOption := &outbound.TrojanOption{}
err = decoder.Decode(mapping, trojanOption) err = decoder.Decode(mapping, trojanOption)
if err != nil { if err != nil {
break break
@@ -170,12 +167,13 @@ func ParseProxy(mapping map[string]any) (C.Proxy, error) {
return nil, err return nil, err
} }
if muxOption.Enabled { if muxOption.Enabled {
proxy, err = outbound.NewSingMux(*muxOption, proxy, proxy.(outbound.ProxyBase)) proxy, err = outbound.NewSingMux(*muxOption, proxy)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
} }
proxy = outbound.NewAutoCloseProxyAdapter(proxy)
return NewProxy(proxy), nil return NewProxy(proxy), nil
} }

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

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

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

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

View File

@@ -275,15 +275,16 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
vmess["skip-cert-verify"] = false vmess["skip-cert-verify"] = false
vmess["cipher"] = "auto" vmess["cipher"] = "auto"
if cipher, ok := values["scy"]; ok && cipher != "" { if cipher, ok := values["scy"].(string); ok && cipher != "" {
vmess["cipher"] = cipher vmess["cipher"] = cipher
} }
if sni, ok := values["sni"]; ok && sni != "" { if sni, ok := values["sni"].(string); ok && sni != "" {
vmess["servername"] = sni vmess["servername"] = sni
} }
network, _ := values["net"].(string) network, ok := values["net"].(string)
if ok {
network = strings.ToLower(network) network = strings.ToLower(network)
if values["type"] == "http" { if values["type"] == "http" {
network = "http" network = "http"
@@ -291,6 +292,7 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
network = "h2" network = "h2"
} }
vmess["network"] = network vmess["network"] = network
}
tls, ok := values["tls"].(string) tls, ok := values["tls"].(string)
if ok { if ok {
@@ -307,12 +309,12 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
case "http": case "http":
headers := make(map[string]any) headers := make(map[string]any)
httpOpts := make(map[string]any) httpOpts := make(map[string]any)
if host, ok := values["host"]; ok && host != "" { if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = []string{host.(string)} headers["Host"] = []string{host}
} }
httpOpts["path"] = []string{"/"} httpOpts["path"] = []string{"/"}
if path, ok := values["path"]; ok && path != "" { if path, ok := values["path"].(string); ok && path != "" {
httpOpts["path"] = []string{path.(string)} httpOpts["path"] = []string{path}
} }
httpOpts["headers"] = headers httpOpts["headers"] = headers
@@ -321,8 +323,8 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
case "h2": case "h2":
headers := make(map[string]any) headers := make(map[string]any)
h2Opts := make(map[string]any) h2Opts := make(map[string]any)
if host, ok := values["host"]; ok && host != "" { if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = []string{host.(string)} headers["Host"] = []string{host}
} }
h2Opts["path"] = values["path"] h2Opts["path"] = values["path"]
@@ -334,11 +336,11 @@ func ConvertsV2Ray(buf []byte) ([]map[string]any, error) {
headers := make(map[string]any) headers := make(map[string]any)
wsOpts := make(map[string]any) wsOpts := make(map[string]any)
wsOpts["path"] = "/" wsOpts["path"] = "/"
if host, ok := values["host"]; ok && host != "" { if host, ok := values["host"].(string); ok && host != "" {
headers["Host"] = host.(string) headers["Host"] = host
} }
if path, ok := values["path"]; ok && path != "" { if path, ok := values["path"].(string); ok && path != "" {
path := path.(string) path := path
pathURL, err := url.Parse(path) pathURL, err := url.Parse(path)
if err == nil { if err == nil {
query := pathURL.Query() query := pathURL.Query()

View File

@@ -3,29 +3,37 @@ package net
import ( import (
"context" "context"
"net" "net"
"github.com/metacubex/mihomo/common/contextutils"
) )
// SetupContextForConn is a helper function that starts connection I/O interrupter goroutine. // SetupContextForConn is a helper function that starts connection I/O interrupter.
// if ctx be canceled before done called, it will close the connection.
// should use like this:
//
// func streamConn(ctx context.Context, conn net.Conn) (_ net.Conn, err error) {
// if ctx.Done() != nil {
// done := N.SetupContextForConn(ctx, conn)
// defer done(&err)
// }
// conn, err := xxx
// return conn, err
// }
func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) { func SetupContextForConn(ctx context.Context, conn net.Conn) (done func(*error)) {
var ( stopc := make(chan struct{})
quit = make(chan struct{}) stop := contextutils.AfterFunc(ctx, func() {
interrupt = make(chan error, 1)
)
go func() {
select {
case <-quit:
interrupt <- nil
case <-ctx.Done():
// Close the connection, discarding the error // Close the connection, discarding the error
_ = conn.Close() _ = conn.Close()
interrupt <- ctx.Err() close(stopc)
} })
}()
return func(inputErr *error) { return func(inputErr *error) {
close(quit) if !stop() {
if ctxErr := <-interrupt; ctxErr != nil && inputErr != nil { // The AfterFunc was started, wait for it to complete.
<-stopc
if ctxErr := ctx.Err(); ctxErr != nil && inputErr != nil {
// Return context error to user. // Return context error to user.
inputErr = &ctxErr *inputErr = ctxErr
}
} }
} }
} }

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

View File

@@ -77,6 +77,6 @@ func (c *refConn) WriterReplaceable() bool { // Relay() will handle reference
var _ ExtendedConn = (*refConn)(nil) var _ ExtendedConn = (*refConn)(nil)
func NewRefConn(conn net.Conn, ref any) net.Conn { func NewRefConn(conn net.Conn, ref any) ExtendedConn {
return &refConn{conn: NewExtendedConn(conn), ref: ref} return &refConn{conn: NewExtendedConn(conn), ref: ref}
} }

View File

@@ -3,8 +3,10 @@ package net
import ( import (
"crypto/rand" "crypto/rand"
"crypto/rsa" "crypto/rsa"
"crypto/sha256"
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"encoding/hex"
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"math/big" "math/big"
@@ -16,7 +18,11 @@ type Path interface {
func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) { func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, error) {
if certificate == "" && privateKey == "" { if certificate == "" && privateKey == "" {
return newRandomTLSKeyPair() var err error
certificate, privateKey, _, err = NewRandomTLSKeyPair()
if err != nil {
return tls.Certificate{}, err
}
} }
cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey)) cert, painTextErr := tls.X509KeyPair([]byte(certificate), []byte(privateKey))
if painTextErr == nil { if painTextErr == nil {
@@ -32,10 +38,10 @@ func ParseCert(certificate, privateKey string, path Path) (tls.Certificate, erro
return cert, nil return cert, nil
} }
func newRandomTLSKeyPair() (tls.Certificate, error) { func NewRandomTLSKeyPair() (certificate string, privateKey string, fingerprint string, err error) {
key, err := rsa.GenerateKey(rand.Reader, 2048) key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil { if err != nil {
return tls.Certificate{}, err return
} }
template := x509.Certificate{SerialNumber: big.NewInt(1)} template := x509.Certificate{SerialNumber: big.NewInt(1)}
certDER, err := x509.CreateCertificate( certDER, err := x509.CreateCertificate(
@@ -45,14 +51,15 @@ func newRandomTLSKeyPair() (tls.Certificate, error) {
&key.PublicKey, &key.PublicKey,
key) key)
if err != nil { if err != nil {
return tls.Certificate{}, err return
} }
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}) cert, err := x509.ParseCertificate(certDER)
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
if err != nil { if err != nil {
return tls.Certificate{}, err return
} }
return tlsCert, nil hash := sha256.Sum256(cert.Raw)
fingerprint = hex.EncodeToString(hash[:])
privateKey = string(pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}))
certificate = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}))
return
} }

View File

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

View File

@@ -10,6 +10,7 @@ type Observable[T any] struct {
listener map[Subscription[T]]*Subscriber[T] listener map[Subscription[T]]*Subscriber[T]
mux sync.Mutex mux sync.Mutex
done bool done bool
stopCh chan struct{}
} }
func (o *Observable[T]) process() { func (o *Observable[T]) process() {
@@ -31,6 +32,7 @@ func (o *Observable[T]) close() {
for _, sub := range o.listener { for _, sub := range o.listener {
sub.Close() sub.Close()
} }
close(o.stopCh)
} }
func (o *Observable[T]) Subscribe() (Subscription[T], error) { func (o *Observable[T]) Subscribe() (Subscription[T], error) {
@@ -59,6 +61,7 @@ func NewObservable[T any](iter Iterable[T]) *Observable[T] {
observable := &Observable[T]{ observable := &Observable[T]{
iterable: iter, iterable: iter,
listener: map[Subscription[T]]*Subscriber[T]{}, listener: map[Subscription[T]]*Subscriber[T]{},
stopCh: make(chan struct{}),
} }
go observable.process() go observable.process()
return observable return observable

View File

@@ -70,9 +70,11 @@ func TestObservable_SubscribeClosedSource(t *testing.T) {
src := NewObservable[int](iter) src := NewObservable[int](iter)
data, _ := src.Subscribe() data, _ := src.Subscribe()
<-data <-data
select {
_, closed := src.Subscribe() case <-src.stopCh:
assert.NotNil(t, closed) case <-time.After(time.Second):
assert.Fail(t, "timeout not stop")
}
} }
func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) { func TestObservable_UnSubscribeWithNotExistSubscription(t *testing.T) {

View File

@@ -22,9 +22,10 @@ func sleepAndSend[T any](ctx context.Context, delay int, input T) func() (T, err
} }
func TestPicker_Basic(t *testing.T) { func TestPicker_Basic(t *testing.T) {
t.Parallel()
picker, ctx := WithContext[int](context.Background()) picker, ctx := WithContext[int](context.Background())
picker.Go(sleepAndSend(ctx, 30, 2)) picker.Go(sleepAndSend(ctx, 200, 2))
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 100, 1))
number := picker.Wait() number := picker.Wait()
assert.NotNil(t, number) assert.NotNil(t, number)
@@ -32,8 +33,9 @@ func TestPicker_Basic(t *testing.T) {
} }
func TestPicker_Timeout(t *testing.T) { func TestPicker_Timeout(t *testing.T) {
t.Parallel()
picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5) picker, ctx := WithTimeout[int](context.Background(), time.Millisecond*5)
picker.Go(sleepAndSend(ctx, 20, 1)) picker.Go(sleepAndSend(ctx, 100, 1))
number := picker.Wait() number := picker.Wait()
assert.Equal(t, number, lo.Empty[int]()) assert.Equal(t, number, lo.Empty[int]())

View File

@@ -13,7 +13,6 @@ type call[T any] struct {
type Single[T any] struct { type Single[T any] struct {
mux sync.Mutex mux sync.Mutex
last time.Time
wait time.Duration wait time.Duration
call *call[T] call *call[T]
result *Result[T] result *Result[T]
@@ -22,16 +21,18 @@ type Single[T any] struct {
type Result[T any] struct { type Result[T any] struct {
Val T Val T
Err error Err error
Time time.Time
} }
// Do single.Do likes sync.singleFlight // Do single.Do likes sync.singleFlight
func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) { func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
s.mux.Lock() s.mux.Lock()
now := time.Now() result := s.result
if now.Before(s.last.Add(s.wait)) { if result != nil && time.Since(result.Time) < s.wait {
s.mux.Unlock() s.mux.Unlock()
return s.result.Val, s.result.Err, true return result.Val, result.Err, true
} }
s.result = nil // The result has expired, clear it
if callM := s.call; callM != nil { if callM := s.call; callM != nil {
s.mux.Unlock() s.mux.Unlock()
@@ -47,15 +48,19 @@ func (s *Single[T]) Do(fn func() (T, error)) (v T, err error, shared bool) {
callM.wg.Done() callM.wg.Done()
s.mux.Lock() s.mux.Lock()
if s.call == callM { // maybe reset when fn is running
s.call = nil s.call = nil
s.result = &Result[T]{callM.val, callM.err} s.result = &Result[T]{callM.val, callM.err, time.Now()}
s.last = now }
s.mux.Unlock() s.mux.Unlock()
return callM.val, callM.err, false return callM.val, callM.err, false
} }
func (s *Single[T]) Reset() { func (s *Single[T]) Reset() {
s.last = time.Time{} s.mux.Lock()
s.call = nil
s.result = nil
s.mux.Unlock()
} }
func NewSingle[T any](wait time.Duration) *Single[T] { func NewSingle[T any](wait time.Duration) *Single[T] {

View File

@@ -11,12 +11,13 @@ import (
) )
func TestBasic(t *testing.T) { func TestBasic(t *testing.T) {
single := NewSingle[int](time.Millisecond * 30) t.Parallel()
single := NewSingle[int](time.Millisecond * 200)
foo := 0 foo := 0
shardCount := atomic.NewInt32(0) shardCount := atomic.NewInt32(0)
call := func() (int, error) { call := func() (int, error) {
foo++ foo++
time.Sleep(time.Millisecond * 5) time.Sleep(time.Millisecond * 20)
return 0, nil return 0, nil
} }
@@ -39,7 +40,8 @@ func TestBasic(t *testing.T) {
} }
func TestTimer(t *testing.T) { func TestTimer(t *testing.T) {
single := NewSingle[int](time.Millisecond * 30) t.Parallel()
single := NewSingle[int](time.Millisecond * 200)
foo := 0 foo := 0
callM := func() (int, error) { callM := func() (int, error) {
foo++ foo++
@@ -47,7 +49,7 @@ func TestTimer(t *testing.T) {
} }
_, _, _ = single.Do(callM) _, _, _ = single.Do(callM)
time.Sleep(10 * time.Millisecond) time.Sleep(100 * time.Millisecond)
_, _, shard := single.Do(callM) _, _, shard := single.Do(callM)
assert.Equal(t, 1, foo) assert.Equal(t, 1, foo)
@@ -55,7 +57,8 @@ func TestTimer(t *testing.T) {
} }
func TestReset(t *testing.T) { func TestReset(t *testing.T) {
single := NewSingle[int](time.Millisecond * 30) t.Parallel()
single := NewSingle[int](time.Millisecond * 200)
foo := 0 foo := 0
callM := func() (int, error) { callM := func() (int, error) {
foo++ foo++

View File

@@ -16,6 +16,17 @@ func Filter[T comparable](tSlice []T, filter func(t T) bool) []T {
return result return result
} }
func Map[T any, N any](arr []T, block func(it T) N) []N {
if arr == nil { // keep nil
return nil
}
retArr := make([]N, 0, len(arr))
for index := range arr {
retArr = append(retArr, block(arr[index]))
}
return retArr
}
func ToStringSlice(value any) ([]string, error) { func ToStringSlice(value any) ([]string, error) {
strArr := make([]string, 0) strArr := make([]string, 0)
switch reflect.TypeOf(value).Kind() { switch reflect.TypeOf(value).Kind() {

View File

@@ -111,11 +111,7 @@ func convertFingerprint(fingerprint string) (*[32]byte, error) {
return (*[32]byte)(fpByte), nil return (*[32]byte)(fpByte), nil
} }
// GetTLSConfig specified fingerprint, customCA and customCAString func GetCertPool(customCA string, customCAString string) (*x509.CertPool, error) {
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (*tls.Config, error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
var certificate []byte var certificate []byte
var err error var err error
if len(customCA) > 0 { if len(customCA) > 0 {
@@ -131,18 +127,35 @@ func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, cu
if !certPool.AppendCertsFromPEM(certificate) { if !certPool.AppendCertsFromPEM(certificate) {
return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate) return nil, fmt.Errorf("failed to parse certificate:\n\n %s", certificate)
} }
tlsConfig.RootCAs = certPool return certPool, nil
} else { } else {
tlsConfig.RootCAs = getCertPool() return getCertPool(), nil
} }
if len(fingerprint) > 0 { }
var fingerprintBytes *[32]byte
fingerprintBytes, err = convertFingerprint(fingerprint) func NewFingerprintVerifier(fingerprint string) (func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error, error) {
fingerprintBytes, err := convertFingerprint(fingerprint)
if err != nil {
return nil, err
}
return verifyFingerprint(fingerprintBytes), nil
}
// GetTLSConfig specified fingerprint, customCA and customCAString
func GetTLSConfig(tlsConfig *tls.Config, fingerprint string, customCA string, customCAString string) (_ *tls.Config, err error) {
if tlsConfig == nil {
tlsConfig = &tls.Config{}
}
tlsConfig.RootCAs, err = GetCertPool(customCA, customCAString)
if err != nil {
return nil, err
}
if len(fingerprint) > 0 {
tlsConfig.VerifyPeerCertificate, err = NewFingerprintVerifier(fingerprint)
if err != nil { if err != nil {
return nil, err return nil, err
} }
tlsConfig = GetGlobalTLSConfig(tlsConfig)
tlsConfig.VerifyPeerCertificate = verifyFingerprint(fingerprintBytes)
tlsConfig.InsecureSkipVerify = true tlsConfig.InsecureSkipVerify = true
} }
return tlsConfig, nil return tlsConfig, nil

View File

@@ -6,7 +6,6 @@ import (
"net" "net"
"net/netip" "net/netip"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/component/iface" "github.com/metacubex/mihomo/component/iface"
"github.com/insomniacslk/dhcp/dhcpv4" "github.com/insomniacslk/dhcp/dhcpv4"
@@ -86,12 +85,14 @@ func receiveOffer(conn net.PacketConn, id dhcpv4.TransactionID, result chan<- []
return return
} }
dnsAddr := make([]netip.Addr, l) results := make([]netip.Addr, 0, len(dns))
for i := 0; i < l; i++ { for _, ip := range dns {
dnsAddr[i] = nnip.IpToAddr(dns[i]) if addr, ok := netip.AddrFromSlice(ip); ok {
results = append(results, addr.Unmap())
}
} }
result <- dnsAddr result <- results
return return
} }

View File

@@ -7,14 +7,12 @@ import (
"net" "net"
"net/netip" "net/netip"
"os" "os"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
"github.com/metacubex/mihomo/component/keepalive" "github.com/metacubex/mihomo/component/keepalive"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
"github.com/metacubex/mihomo/log"
) )
const ( const (
@@ -22,34 +20,16 @@ const (
DefaultUDPTimeout = DefaultTCPTimeout DefaultUDPTimeout = DefaultTCPTimeout
) )
type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) type dialFunc func(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error)
var ( var (
dialMux sync.Mutex dialMux sync.Mutex
IP4PEnable bool
actualSingleStackDialContext = serialSingleStackDialContext actualSingleStackDialContext = serialSingleStackDialContext
actualDualStackDialContext = serialDualStackDialContext actualDualStackDialContext = serialDualStackDialContext
tcpConcurrent = false tcpConcurrent = false
fallbackTimeout = 300 * time.Millisecond fallbackTimeout = 300 * time.Millisecond
) )
func applyOptions(options ...Option) *option {
opt := &option{
interfaceName: DefaultInterface.Load(),
routingMark: int(DefaultRoutingMark.Load()),
}
for _, o := range DefaultOptions {
o(opt)
}
for _, o := range options {
o(opt)
}
return opt
}
func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) {
opt := applyOptions(options...) opt := applyOptions(options...)
@@ -79,38 +59,43 @@ func DialContext(ctx context.Context, network, address string, options ...Option
} }
func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) { func ListenPacket(ctx context.Context, network, address string, rAddrPort netip.AddrPort, options ...Option) (net.PacketConn, error) {
cfg := applyOptions(options...) opt := applyOptions(options...)
lc := &net.ListenConfig{} lc := &net.ListenConfig{}
if cfg.addrReuse { if opt.addrReuse {
addrReuseToListenConfig(lc) addrReuseToListenConfig(lc)
} }
if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark when DefaultSocketHook not null (in CMFA)
socketHookToListenConfig(lc) socketHookToListenConfig(lc)
} else { } else {
interfaceName := cfg.interfaceName if opt.interfaceName == "" {
if interfaceName == "" { opt.interfaceName = DefaultInterface.Load()
}
if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil { if finder := DefaultInterfaceFinder.Load(); finder != nil {
interfaceName = finder.FindInterfaceName(rAddrPort.Addr()) opt.interfaceName = finder.FindInterfaceName(rAddrPort.Addr())
} }
} }
if rAddrPort.Addr().Unmap().IsLoopback() { if rAddrPort.Addr().Unmap().IsLoopback() {
// avoid "The requested address is not valid in its context." // avoid "The requested address is not valid in its context."
interfaceName = "" opt.interfaceName = ""
} }
if interfaceName != "" { if opt.interfaceName != "" {
bind := bindIfaceToListenConfig bind := bindIfaceToListenConfig
if cfg.fallbackBind { if opt.fallbackBind {
bind = fallbackBindIfaceToListenConfig bind = fallbackBindIfaceToListenConfig
} }
addr, err := bind(interfaceName, lc, network, address, rAddrPort) addr, err := bind(opt.interfaceName, lc, network, address, rAddrPort)
if err != nil { if err != nil {
return nil, err return nil, err
} }
address = addr address = addr
} }
if cfg.routingMark != 0 { if opt.routingMark == 0 {
bindMarkToListenConfig(cfg.routingMark, lc, network, address) opt.routingMark = int(DefaultRoutingMark.Load())
}
if opt.routingMark != 0 {
bindMarkToListenConfig(opt.routingMark, lc, network, address)
} }
} }
@@ -136,11 +121,9 @@ func GetTcpConcurrent() bool {
return tcpConcurrent return tcpConcurrent
} }
func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt *option) (net.Conn, error) { func dialContext(ctx context.Context, network string, destination netip.Addr, port string, opt option) (net.Conn, error) {
var address string var address string
if IP4PEnable { destination, port = resolver.LookupIP4P(destination, port)
destination, port = lookupIP4P(destination, port)
}
address = net.JoinHostPort(destination.String(), port) address = net.JoinHostPort(destination.String(), port)
netDialer := opt.netDialer netDialer := opt.netDialer
@@ -163,21 +146,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA) if DefaultSocketHook != nil { // ignore interfaceName, routingMark and tfo when DefaultSocketHook not null (in CMFA)
socketHookToToDialer(dialer) socketHookToToDialer(dialer)
} else { } else {
interfaceName := opt.interfaceName // don't change the "opt", it's a pointer if opt.interfaceName == "" {
if interfaceName == "" { opt.interfaceName = DefaultInterface.Load()
}
if opt.interfaceName == "" {
if finder := DefaultInterfaceFinder.Load(); finder != nil { if finder := DefaultInterfaceFinder.Load(); finder != nil {
interfaceName = finder.FindInterfaceName(destination) opt.interfaceName = finder.FindInterfaceName(destination)
} }
} }
if interfaceName != "" { if opt.interfaceName != "" {
bind := bindIfaceToDialer bind := bindIfaceToDialer
if opt.fallbackBind { if opt.fallbackBind {
bind = fallbackBindIfaceToDialer bind = fallbackBindIfaceToDialer
} }
if err := bind(interfaceName, dialer, network, destination); err != nil { if err := bind(opt.interfaceName, dialer, network, destination); err != nil {
return nil, err return nil, err
} }
} }
if opt.routingMark == 0 {
opt.routingMark = int(DefaultRoutingMark.Load())
}
if opt.routingMark != 0 { if opt.routingMark != 0 {
bindMarkToDialer(opt.routingMark, dialer, network, destination) bindMarkToDialer(opt.routingMark, dialer, network, destination)
} }
@@ -189,26 +177,26 @@ func dialContext(ctx context.Context, network string, destination netip.Addr, po
return dialer.DialContext(ctx, network, address) return dialer.DialContext(ctx, network, address)
} }
func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func serialSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return serialDialContext(ctx, network, ips, port, opt) return serialDialContext(ctx, network, ips, port, opt)
} }
func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func serialDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt) return dualStackDialContext(ctx, serialDialContext, network, ips, port, opt)
} }
func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func concurrentSingleStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
return parallelDialContext(ctx, network, ips, port, opt) return parallelDialContext(ctx, network, ips, port, opt)
} }
func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func concurrentDualStackDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if opt.prefer != 4 && opt.prefer != 6 { if opt.prefer != 4 && opt.prefer != 6 {
return parallelDialContext(ctx, network, ips, port, opt) return parallelDialContext(ctx, network, ips, port, opt)
} }
return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt) return dualStackDialContext(ctx, parallelDialContext, network, ips, port, opt)
} }
func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func dualStackDialContext(ctx context.Context, dialFn dialFunc, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
ipv4s, ipv6s := resolver.SortationAddr(ips) ipv4s, ipv6s := resolver.SortationAddr(ips)
if len(ipv4s) == 0 && len(ipv6s) == 0 { if len(ipv4s) == 0 && len(ipv6s) == 0 {
return nil, ErrorNoIpAddress return nil, ErrorNoIpAddress
@@ -289,7 +277,7 @@ loop:
return nil, errors.Join(errs...) return nil, errors.Join(errs...)
} }
func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func parallelDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 { if len(ips) == 0 {
return nil, ErrorNoIpAddress return nil, ErrorNoIpAddress
} }
@@ -328,7 +316,7 @@ func parallelDialContext(ctx context.Context, network string, ips []netip.Addr,
return nil, os.ErrDeadlineExceeded return nil, os.ErrDeadlineExceeded
} }
func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt *option) (net.Conn, error) { func serialDialContext(ctx context.Context, network string, ips []netip.Addr, port string, opt option) (net.Conn, error) {
if len(ips) == 0 { if len(ips) == 0 {
return nil, ErrorNoIpAddress return nil, ErrorNoIpAddress
} }
@@ -394,23 +382,5 @@ func (d Dialer) ListenPacket(ctx context.Context, network, address string, rAddr
func NewDialer(options ...Option) Dialer { func NewDialer(options ...Option) Dialer {
opt := applyOptions(options...) opt := applyOptions(options...)
return Dialer{Opt: *opt} return Dialer{Opt: opt}
}
func GetIP4PEnable(enableIP4PConvert bool) {
IP4PEnable = enableIP4PConvert
}
// kanged from https://github.com/heiher/frp/blob/ip4p/client/ip4p.go
func lookupIP4P(addr netip.Addr, port string) (netip.Addr, string) {
ip := addr.AsSlice()
if ip[0] == 0x20 && ip[1] == 0x01 &&
ip[2] == 0x00 && ip[3] == 0x00 {
addr = netip.AddrFrom4([4]byte{ip[12], ip[13], ip[14], ip[15]})
port = strconv.Itoa(int(ip[10])<<8 + int(ip[11]))
log.Debugln("Convert IP4P address %s to %s", ip, net.JoinHostPort(addr.String(), port))
return addr, port
}
return addr, port
} }

View File

@@ -10,7 +10,6 @@ import (
) )
var ( var (
DefaultOptions []Option
DefaultInterface = atomic.NewTypedValue[string]("") DefaultInterface = atomic.NewTypedValue[string]("")
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
@@ -115,3 +114,15 @@ func WithOption(o option) Option {
*opt = o *opt = o
} }
} }
func IsZeroOptions(opts []Option) bool {
return applyOptions(opts...) == option{}
}
func applyOptions(options ...Option) option {
opt := option{}
for _, o := range options {
o(&opt)
}
return opt
}

View File

@@ -6,9 +6,10 @@ import (
"strings" "strings"
"sync" "sync"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/component/profile/cachefile" "github.com/metacubex/mihomo/component/profile/cachefile"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"go4.org/netipx"
) )
const ( const (
@@ -183,7 +184,7 @@ func New(options Options) (*Pool, error) {
hostAddr = options.IPNet.Masked().Addr() hostAddr = options.IPNet.Masked().Addr()
gateway = hostAddr.Next() gateway = hostAddr.Next()
first = gateway.Next().Next().Next() // default start with 198.18.0.4 first = gateway.Next().Next().Next() // default start with 198.18.0.4
last = nnip.UnMasked(options.IPNet) last = netipx.PrefixLastIP(options.IPNet)
) )
if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) { if !options.IPNet.IsValid() || !first.IsValid() || !first.Less(last) {

View File

@@ -69,7 +69,7 @@ func HttpRequestWithProxy(ctx context.Context, url, method string, header map[st
TLSHandshakeTimeout: 10 * time.Second, TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second, ExpectContinueTimeout: 1 * time.Second,
DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { DialContext: func(ctx context.Context, network, address string) (net.Conn, error) {
if conn, err := inner.HandleTcp(address, specialProxy); err == nil { if conn, err := inner.HandleTcp(inner.GetTunnel(), address, specialProxy); err == nil {
return conn, nil return conn, nil
} else { } else {
return dialer.DialContext(ctx, network, address) return dialer.DialContext(ctx, network, address)

View File

@@ -59,7 +59,7 @@ func SetNetListenConfig(lc *net.ListenConfig) {
} }
func TCPKeepAlive(c net.Conn) { func TCPKeepAlive(c net.Conn) {
if tcp, ok := c.(*net.TCPConn); ok && tcp != nil { if tcp, ok := c.(TCPConn); ok && tcp != nil {
tcpKeepAlive(tcp) tcpKeepAlive(tcp)
} }
} }

View File

@@ -2,9 +2,18 @@
package keepalive package keepalive
import "net" import (
"net"
"time"
)
func tcpKeepAlive(tcp *net.TCPConn) { type TCPConn interface {
net.Conn
SetKeepAlive(keepalive bool) error
SetKeepAlivePeriod(d time.Duration) error
}
func tcpKeepAlive(tcp TCPConn) {
if DisableKeepAlive() { if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false) _ = tcp.SetKeepAlive(false)
} else { } else {

View File

@@ -4,6 +4,12 @@ package keepalive
import "net" import "net"
type TCPConn interface {
net.Conn
SetKeepAlive(keepalive bool) error
SetKeepAliveConfig(config net.KeepAliveConfig) error
}
func keepAliveConfig() net.KeepAliveConfig { func keepAliveConfig() net.KeepAliveConfig {
config := net.KeepAliveConfig{ config := net.KeepAliveConfig{
Enable: true, Enable: true,
@@ -18,7 +24,7 @@ func keepAliveConfig() net.KeepAliveConfig {
return config return config
} }
func tcpKeepAlive(tcp *net.TCPConn) { func tcpKeepAlive(tcp TCPConn) {
if DisableKeepAlive() { if DisableKeepAlive() {
_ = tcp.SetKeepAlive(false) _ = tcp.SetKeepAlive(false)
} else { } else {

View File

@@ -87,6 +87,7 @@ func findProcessName(network string, ip netip.Addr, port int) (uint32, string, e
default: default:
continue continue
} }
srcIP = srcIP.Unmap()
if ip == srcIP { if ip == srcIP {
// xsocket_n.so_last_pid // xsocket_n.so_last_pid

View File

@@ -10,7 +10,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
) )
@@ -136,13 +135,14 @@ func (s *searcher) Search(buf []byte, ip netip.Addr, port uint16, isTCP bool) (u
switch { switch {
case flag&0x1 > 0 && isIPv4: case flag&0x1 > 0 && isIPv4:
// ipv4 // ipv4
srcIP = nnip.IpToAddr(buf[inp+s.ip : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip : inp+s.ip+4])
case flag&0x2 > 0 && !isIPv4: case flag&0x2 > 0 && !isIPv4:
// ipv6 // ipv6
srcIP = nnip.IpToAddr(buf[inp+s.ip-12 : inp+s.ip+4]) srcIP, _ = netip.AddrFromSlice(buf[inp+s.ip-12 : inp+s.ip+4])
default: default:
continue continue
} }
srcIP = srcIP.Unmap()
if ip != srcIP { if ip != srcIP {
continue continue

View File

@@ -7,7 +7,6 @@ import (
"syscall" "syscall"
"unsafe" "unsafe"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@@ -137,7 +136,8 @@ func (s *searcher) Search(b []byte, ip netip.Addr, port uint16) (uint32, error)
continue continue
} }
srcIP := nnip.IpToAddr(row[s.ip : s.ip+s.ipSize]) srcIP, _ := netip.AddrFromSlice(row[s.ip : s.ip+s.ipSize])
srcIP = srcIP.Unmap()
// windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto // windows binds an unbound udp socket to 0.0.0.0/[::] while first sendto
if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) { if ip != srcIP && (!srcIP.IsUnspecified() || s.tcpState != -1) {
continue continue

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

View File

@@ -196,6 +196,26 @@ func ResolveIP(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPWithResolver(ctx, host, DefaultResolver) return ResolveIPWithResolver(ctx, host, DefaultResolver)
} }
// ResolveIPPrefer6WithResolver same as ResolveIP, but with a resolver
func ResolveIPPrefer6WithResolver(ctx context.Context, host string, r Resolver) (netip.Addr, error) {
ips, err := LookupIPWithResolver(ctx, host, r)
if err != nil {
return netip.Addr{}, err
} else if len(ips) == 0 {
return netip.Addr{}, fmt.Errorf("%w: %s", ErrIPNotFound, host)
}
ipv4s, ipv6s := SortationAddr(ips)
if len(ipv6s) > 0 {
return ipv6s[randv2.IntN(len(ipv6s))], nil
}
return ipv4s[randv2.IntN(len(ipv4s))], nil
}
// ResolveIPPrefer6 with a host, return ip and priority return TypeAAAA
func ResolveIPPrefer6(ctx context.Context, host string) (netip.Addr, error) {
return ResolveIPPrefer6WithResolver(ctx, host, DefaultResolver)
}
func ResetConnection() { func ResetConnection() {
if DefaultResolver != nil { if DefaultResolver != nil {
go DefaultResolver.ResetConnection() go DefaultResolver.ResetConnection()

View File

@@ -399,9 +399,7 @@ func (q *quicPacketSender) readQuicData(b []byte) error {
} }
} }
_ = q.tryAssemble() return q.tryAssemble()
return nil
} }
func (q *quicPacketSender) tryAssemble() error { func (q *quicPacketSender) tryAssemble() error {
@@ -415,7 +413,17 @@ func (q *quicPacketSender) tryAssemble() error {
if len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(len(q.buffer)) { if len(q.ranges) != 1 || q.ranges[0].Start() != 0 || q.ranges[0].End() != uint64(len(q.buffer)) {
q.lock.RUnlock() q.lock.RUnlock()
return ErrNoClue // incomplete fragment, just return
return nil
}
if len(q.buffer) <= 4 ||
// Handshake Type (1) + uint24 Length (3) + ClientHello body
// maxCryptoStreamOffset is in the valid range of uint16 so just ignore the q.buffer[1]
int(binary.BigEndian.Uint16([]byte{q.buffer[2], q.buffer[3]})+4) != len(q.buffer) {
q.lock.RUnlock()
// end of segment not reached, just return
return nil
} }
domain, err := ReadClientHello(q.buffer) domain, err := ReadClientHello(q.buffer)

View File

@@ -26,6 +26,7 @@ import (
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf" "golang.org/x/crypto/hkdf"
"golang.org/x/exp/slices"
"golang.org/x/net/http2" "golang.org/x/net/http2"
) )
@@ -36,9 +37,9 @@ type RealityConfig struct {
ShortID [RealityMaxShortIDLen]byte ShortID [RealityMaxShortIDLen]byte
} }
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) { func GetRealityConn(ctx context.Context, conn net.Conn, clientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
retry := 0 retry := 0
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ { for fingerprint, exists := GetFingerprint(clientFingerprint); exists; retry++ {
verifier := &realityVerifier{ verifier := &realityVerifier{
serverName: tlsConfig.ServerName, serverName: tlsConfig.ServerName,
} }
@@ -60,6 +61,27 @@ func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string
return nil, err return nil, err
} }
// ------for X25519MLKEM768 does not work properly with reality-------
// Iterate over extensions and check
for _, extension := range uConn.Extensions {
if ce, ok := extension.(*utls.SupportedCurvesExtension); ok {
ce.Curves = slices.DeleteFunc(ce.Curves, func(curveID utls.CurveID) bool {
return curveID == utls.X25519MLKEM768
})
}
if ks, ok := extension.(*utls.KeyShareExtension); ok {
ks.KeyShares = slices.DeleteFunc(ks.KeyShares, func(share utls.KeyShare) bool {
return share.Group == utls.X25519MLKEM768
})
}
}
// Rebuild the client hello
err = uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
// --------------------------------------------------------------------
hello := uConn.HandshakeState.Hello hello := uConn.HandshakeState.Hello
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
for i := range rawSessionID { // https://github.com/golang/go/issues/5373 for i := range rawSessionID { // https://github.com/golang/go/issues/5373

View File

@@ -4,15 +4,16 @@ import (
"crypto/tls" "crypto/tls"
"net" "net"
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
utls "github.com/metacubex/utls" utls "github.com/metacubex/utls"
"github.com/mroth/weightedrand/v2" "github.com/mroth/weightedrand/v2"
) )
type UConn struct { type UConn = utls.UConn
*utls.UConn
} const VersionTLS13 = utls.VersionTLS13
type UClientHelloID struct { type UClientHelloID struct {
*utls.ClientHelloID *utls.ClientHelloID
@@ -21,13 +22,8 @@ type UClientHelloID struct {
var initRandomFingerprint UClientHelloID var initRandomFingerprint UClientHelloID
var initUtlsClient string var initUtlsClient string
func UClient(c net.Conn, config *tls.Config, fingerprint UClientHelloID) *UConn { func UClient(c net.Conn, config *utls.Config, fingerprint UClientHelloID) *UConn {
utlsConn := utls.UClient(c, copyConfig(config), utls.ClientHelloID{ return utls.UClient(c, config, *fingerprint.ClientHelloID)
Client: fingerprint.Client,
Version: fingerprint.Version,
Seed: fingerprint.Seed,
})
return &UConn{UConn: utlsConn}
} }
func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) { func GetFingerprint(ClientFingerprint string) (UClientHelloID, bool) {
@@ -95,18 +91,43 @@ func init() {
Fingerprints["randomized"] = UClientHelloID{&randomized} Fingerprints["randomized"] = UClientHelloID{&randomized}
} }
func copyConfig(c *tls.Config) *utls.Config { func UCertificates(it tls.Certificate) utls.Certificate {
return utls.Certificate{
Certificate: it.Certificate,
PrivateKey: it.PrivateKey,
SupportedSignatureAlgorithms: utils.Map(it.SupportedSignatureAlgorithms, func(it tls.SignatureScheme) utls.SignatureScheme {
return utls.SignatureScheme(it)
}),
OCSPStaple: it.OCSPStaple,
SignedCertificateTimestamps: it.SignedCertificateTimestamps,
Leaf: it.Leaf,
}
}
func UConfig(config *tls.Config) *utls.Config {
return &utls.Config{ return &utls.Config{
RootCAs: c.RootCAs, Rand: config.Rand,
ServerName: c.ServerName, Time: config.Time,
InsecureSkipVerify: c.InsecureSkipVerify, Certificates: utils.Map(config.Certificates, UCertificates),
VerifyPeerCertificate: c.VerifyPeerCertificate, VerifyPeerCertificate: config.VerifyPeerCertificate,
RootCAs: config.RootCAs,
NextProtos: config.NextProtos,
ServerName: config.ServerName,
InsecureSkipVerify: config.InsecureSkipVerify,
CipherSuites: config.CipherSuites,
MinVersion: config.MinVersion,
MaxVersion: config.MaxVersion,
CurvePreferences: utils.Map(config.CurvePreferences, func(it tls.CurveID) utls.CurveID {
return utls.CurveID(it)
}),
SessionTicketsDisabled: config.SessionTicketsDisabled,
Renegotiation: utls.RenegotiationSupport(config.Renegotiation),
} }
} }
// BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN. // BuildWebsocketHandshakeState it will only send http/1.1 in its ALPN.
// Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go // Copy from https://github.com/XTLS/Xray-core/blob/main/transport/internet/tls/tls.go
func (c *UConn) BuildWebsocketHandshakeState() error { func BuildWebsocketHandshakeState(c *UConn) error {
// Build the handshake state. This will apply every variable of the TLS of the // Build the handshake state. This will apply every variable of the TLS of the
// fingerprint in the UConn // fingerprint in the UConn
if err := c.BuildHandshakeState(); err != nil { if err := c.BuildHandshakeState(); err != nil {

View File

@@ -279,6 +279,10 @@ type RawTun struct {
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
ExcludeSrcPort []uint16 `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
ExcludeSrcPortRange []string `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
ExcludeDstPort []uint16 `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
ExcludeDstPortRange []string `yaml:"exclude-dst-port-range" json:"exclude-dst-port-range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
@@ -1560,6 +1564,10 @@ func parseTun(rawTun RawTun, general *General) error {
IncludeUIDRange: rawTun.IncludeUIDRange, IncludeUIDRange: rawTun.IncludeUIDRange,
ExcludeUID: rawTun.ExcludeUID, ExcludeUID: rawTun.ExcludeUID,
ExcludeUIDRange: rawTun.ExcludeUIDRange, ExcludeUIDRange: rawTun.ExcludeUIDRange,
ExcludeSrcPort: rawTun.ExcludeSrcPort,
ExcludeSrcPortRange: rawTun.ExcludeSrcPortRange,
ExcludeDstPort: rawTun.ExcludeDstPort,
ExcludeDstPortRange: rawTun.ExcludeDstPortRange,
IncludeAndroidUser: rawTun.IncludeAndroidUser, IncludeAndroidUser: rawTun.IncludeAndroidUser,
IncludePackage: rawTun.IncludePackage, IncludePackage: rawTun.IncludePackage,
ExcludePackage: rawTun.ExcludePackage, ExcludePackage: rawTun.ExcludePackage,

View File

@@ -149,6 +149,9 @@ type ProxyAdapter interface {
// Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract. // Unwrap extracts the proxy from a proxy-group. It returns nil when nothing to extract.
Unwrap(metadata *Metadata, touch bool) Proxy Unwrap(metadata *Metadata, touch bool) Proxy
// Close releasing associated resources
Close() error
} }
type Group interface { type Group interface {

View File

@@ -6,11 +6,15 @@ import (
"net" "net"
"net/netip" "net/netip"
"strconv" "strconv"
"github.com/metacubex/mihomo/transport/socks5"
) )
// Socks addr type // SOCKS address types as defined in RFC 1928 section 5.
const (
AtypIPv4 AddrType = 1
AtypDomainName AddrType = 3
AtypIPv6 AddrType = 4
)
const ( const (
TCP NetWork = iota TCP NetWork = iota
UDP UDP
@@ -37,6 +41,21 @@ const (
INNER INNER
) )
type AddrType byte
func (a AddrType) String() string {
switch a {
case AtypIPv4:
return "IPv4"
case AtypDomainName:
return "DomainName"
case AtypIPv6:
return "IPv6"
default:
return "Unknown"
}
}
type NetWork int type NetWork int
func (n NetWork) String() string { func (n NetWork) String() string {
@@ -207,14 +226,14 @@ func (m *Metadata) SourceValid() bool {
return m.SrcPort != 0 && m.SrcIP.IsValid() return m.SrcPort != 0 && m.SrcIP.IsValid()
} }
func (m *Metadata) AddrType() int { func (m *Metadata) AddrType() AddrType {
switch true { switch true {
case m.Host != "" || !m.DstIP.IsValid(): case m.Host != "" || !m.DstIP.IsValid():
return socks5.AtypDomainName return AtypDomainName
case m.DstIP.Is4(): case m.DstIP.Is4():
return socks5.AtypIPv4 return AtypIPv4
default: default:
return socks5.AtypIPv6 return AtypIPv6
} }
} }

View File

@@ -6,7 +6,6 @@ import (
"time" "time"
"github.com/metacubex/mihomo/common/lru" "github.com/metacubex/mihomo/common/lru"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/component/fakeip" "github.com/metacubex/mihomo/component/fakeip"
R "github.com/metacubex/mihomo/component/resolver" R "github.com/metacubex/mihomo/component/resolver"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -120,14 +119,21 @@ func withMapping(mapping *lru.LruCache[netip.Addr, string]) middleware {
switch a := ans.(type) { switch a := ans.(type) {
case *D.A: case *D.A:
ip = nnip.IpToAddr(a.A) ip, _ = netip.AddrFromSlice(a.A)
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
case *D.AAAA: case *D.AAAA:
ip = nnip.IpToAddr(a.AAAA) ip, _ = netip.AddrFromSlice(a.AAAA)
ttl = a.Hdr.Ttl ttl = a.Hdr.Ttl
default: default:
continue continue
} }
if !ip.IsValid() {
continue
}
if !ip.IsGlobalUnicast() {
continue
}
ip = ip.Unmap()
if ttl < 1 { if ttl < 1 {
ttl = 1 ttl = 1

View File

@@ -19,6 +19,16 @@ func dnsReadConfig() (servers []string, err error) {
return return
} }
for _, aa := range aas { for _, aa := range aas {
// Only take interfaces whose OperStatus is IfOperStatusUp(0x01) into DNS configs.
if aa.OperStatus != windows.IfOperStatusUp {
continue
}
// Only take interfaces which have at least one gateway
if aa.FirstGatewayAddress == nil {
continue
}
for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next { for dns := aa.FirstDnsServerAddress; dns != nil; dns = dns.Next {
sa, err := dns.Address.Sockaddr.Sockaddr() sa, err := dns.Address.Sockaddr.Sockaddr()
if err != nil { if err != nil {
@@ -63,7 +73,8 @@ func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
l := uint32(15000) // recommended initial size l := uint32(15000) // recommended initial size
for { for {
b = make([]byte, l) b = make([]byte, l)
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, windows.GAA_FLAG_INCLUDE_PREFIX, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l) const flags = windows.GAA_FLAG_INCLUDE_PREFIX | windows.GAA_FLAG_INCLUDE_GATEWAYS
err := windows.GetAdaptersAddresses(syscall.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
if err == nil { if err == nil {
if l == 0 { if l == 0 {
return nil, nil return nil, nil

View File

@@ -10,7 +10,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/metacubex/mihomo/common/nnip"
"github.com/metacubex/mihomo/common/picker" "github.com/metacubex/mihomo/common/picker"
"github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/dialer"
"github.com/metacubex/mihomo/component/resolver" "github.com/metacubex/mihomo/component/resolver"
@@ -150,19 +149,24 @@ func handleMsgWithEmptyAnswer(r *D.Msg) *D.Msg {
return msg return msg
} }
func msgToIP(msg *D.Msg) []netip.Addr { func msgToIP(msg *D.Msg) (ips []netip.Addr) {
ips := []netip.Addr{}
for _, answer := range msg.Answer { for _, answer := range msg.Answer {
var ip netip.Addr
switch ans := answer.(type) { switch ans := answer.(type) {
case *D.AAAA: case *D.AAAA:
ips = append(ips, nnip.IpToAddr(ans.AAAA)) ip, _ = netip.AddrFromSlice(ans.AAAA)
case *D.A: case *D.A:
ips = append(ips, nnip.IpToAddr(ans.A)) ip, _ = netip.AddrFromSlice(ans.A)
default:
continue
} }
if !ip.IsValid() {
continue
} }
ip = ip.Unmap()
return ips ips = append(ips, ip)
}
return
} }
func msgToDomain(msg *D.Msg) string { func msgToDomain(msg *D.Msg) string {

View File

@@ -1298,6 +1298,31 @@ listeners:
# password: "example" # password: "example"
### 注意对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ### ### 注意对于trojan listener, 至少需要填写 “certificate和private-key” 或 “reality-config” 或 “ss-option” 的其中一项 ###
- name: hysteria2-in-1
type: hysteria2
port: 10820 # 支持使用ports格式例如200,302 or 200,204,401-429,501-503
listen: 0.0.0.0
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules
# proxy: proxy # 如果不为空则直接将该入站流量交由指定 proxy 处理 (当 proxy 不为空时,这里的 proxy 名称必须合法,否则会出错)
users:
00000000-0000-0000-0000-000000000000: PASSWORD_0
00000000-0000-0000-0000-000000000001: PASSWORD_1
# certificate: ./server.crt
# private-key: ./server.key
## up 和 down 均不写或为 0 则使用 BBR 流控
# up: "30 Mbps" # 若不写单位,默认为 Mbps
# down: "200 Mbps" # 若不写单位,默认为 Mbps
# obfs: salamander # 默认为空,如果填写则开启 obfs目前仅支持 salamander
# obfs-password: yourpassword
# max-idle-time: 15000
# alpn:
# - h3
# ignore-client-bandwidth: false
# HTTP3 服务器认证失败时的行为 URL 字符串配置),如果 masquerade 未配置,则返回 404 页
# masquerade: file:///var/www # 作为文件服务器
# masquerade: http://127.0.0.1:8080 #作为反向代理
# masquerade: https://127.0.0.1:8080 #作为反向代理
- name: tun-in-1 - name: tun-in-1
type: tun type: tun
# rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules # rule: sub-rule-name1 # 默认使用 rules如果未找到 sub-rule 则直接使用 rules

View File

@@ -20,19 +20,19 @@ require (
github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab github.com/metacubex/amneziawg-go v0.0.0-20240922133038-fdf3a4d5a4ab
github.com/metacubex/bart v0.19.0 github.com/metacubex/bart v0.19.0
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399
github.com/metacubex/chacha v0.1.1 github.com/metacubex/chacha v0.1.2
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996
github.com/metacubex/randv2 v0.2.0 github.com/metacubex/randv2 v0.2.0
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925
github.com/metacubex/sing-shadowsocks v0.2.8 github.com/metacubex/sing-shadowsocks v0.2.8
github.com/metacubex/sing-shadowsocks2 v0.2.2 github.com/metacubex/sing-shadowsocks2 v0.2.2
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422
github.com/metacubex/utls v1.6.8-alpha.4 github.com/metacubex/utls v1.7.0-alpha.1
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181
github.com/miekg/dns v1.1.63 github.com/miekg/dns v1.1.63
github.com/mroth/weightedrand/v2 v2.1.0 github.com/mroth/weightedrand/v2 v2.1.0
@@ -44,7 +44,6 @@ require (
github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a
github.com/sagernet/sing v0.5.2 github.com/sagernet/sing v0.5.2
github.com/sagernet/sing-mux v0.2.1 github.com/sagernet/sing-mux v0.2.1
github.com/sagernet/sing-shadowtls v0.1.5
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7
github.com/samber/lo v1.49.1 github.com/samber/lo v1.49.1
github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20 github.com/shirou/gopsutil/v4 v4.25.1 // lastest version compatible with golang1.20

View File

@@ -101,8 +101,8 @@ github.com/metacubex/bart v0.19.0 h1:XQ9AJeI+WO+phRPkUOoflAFwlqDJnm5BPQpixciJQBY
github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI= github.com/metacubex/bart v0.19.0/go.mod h1:DCcyfP4MC+Zy7sLK7XeGuMw+P5K9mIRsYOBgiE8icsI=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399 h1:oBowHVKZycNtAFbZ6avaCSZJYeme2Nrj+4RpV2cNJig=
github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro= github.com/metacubex/bbolt v0.0.0-20240822011022-aed6d4850399/go.mod h1:4xcieuIK+M4bGQmQYZVqEaIYqjS1ahO4kXG7EmDgEro=
github.com/metacubex/chacha v0.1.1 h1:OHIv11Nd9CISAIzegpjfupIoZp9DYm6uQw41RxvmU/c= github.com/metacubex/chacha v0.1.2 h1:QulCq3eVm3TO6+4nVIWJtmSe7BT2GMrgVHuAoqRQnlc=
github.com/metacubex/chacha v0.1.1/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8= github.com/metacubex/chacha v0.1.2/go.mod h1:Djn9bPZxLTXbJFSeyo0/qzEzQI+gUSSzttuzZM75GH8=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759 h1:cjd4biTvOzK9ubNCCkQ+ldc4YSH/rILn53l/xGBFHHI=
github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88= github.com/metacubex/gopacket v1.1.20-0.20230608035415-7e2f98a3e759/go.mod h1:UHOv2xu+RIgLwpXca7TLrXleEd4oR3sPatW6IF8wU88=
github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI= github.com/metacubex/gvisor v0.0.0-20250324165734-5857f47bd43b h1:RUh4OdVPz/jDrM9MQ2ySuqu2aeBqcA8rtfWUYLZ8RtI=
@@ -111,26 +111,26 @@ github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996 h1:B+AP/Pj2/j
github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY= github.com/metacubex/quic-go v0.49.1-0.20250212162123-c135a4412996/go.mod h1:ExVjGyEwTUjCFqx+5uxgV7MOoA3fZI+th4D40H35xmY=
github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs= github.com/metacubex/randv2 v0.2.0 h1:uP38uBvV2SxYfLj53kuvAjbND4RUDfFJjwr4UigMiLs=
github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY= github.com/metacubex/randv2 v0.2.0/go.mod h1:kFi2SzrQ5WuneuoLLCMkABtiBu6VRrMrWFqSPyj2cxY=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629 h1:aHsYiTvubfgMa3JMTDY//hDXVvFWrHg6ARckR52ttZs=
github.com/metacubex/reality v0.0.0-20250219003814-74e8d7850629/go.mod h1:TTeIOZLdGmzc07Oedn++vWUUfkZoXLF4sEMxWuhBFr8=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00= github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a h1:xjPXdDTlIKq4U/KnKpoCtkxD03T8GimtQrvHy/3dN00=
github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= github.com/metacubex/sing v0.0.0-20250228041610-d94509dc612a/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925 h1:UkPoRAnoBQMn7IK5qpoIV3OejU15q+rqel3NrbSCFKA= github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c h1:OB3WmMA8YPJjE36RjD9X8xlrWGJ4orxbf2R/KAE28b0=
github.com/metacubex/sing-quic v0.0.0-20250119013740-2a19cce83925/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8= github.com/metacubex/sing-quic v0.0.0-20250404030904-b2cc8aab562c/go.mod h1:g7Mxj7b7zm7YVqD975mk/hSmrb0A0G4bVvIMr2MMzn8=
github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4= github.com/metacubex/sing-shadowsocks v0.2.8 h1:wIhlaigswzjPw4hej75sEvWte3QR0+AJRafgwBHO5B4=
github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0= github.com/metacubex/sing-shadowsocks v0.2.8/go.mod h1:X3x88XtJpBxG0W0/ECOJL6Ib0SJ3xdniAkU/6/RMWU0=
github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo= github.com/metacubex/sing-shadowsocks2 v0.2.2 h1:eaf42uVx4Lr21S6MDYs0ZdTvGA0GEhDpb9no4+gdXPo=
github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q= github.com/metacubex/sing-shadowsocks2 v0.2.2/go.mod h1:BhOug03a/RbI7y6hp6q+6ITM1dXjnLTmeWBHSTwvv2Q=
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04 h1:B211C+i/I8CWf4I/BaAV0mmkEHrDBJ0XR9EWxjPbFEg= github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63 h1:vy/8ZYYtWUXYnOnw/NF8ThG1W/RqM/h5rkun+OXZMH0=
github.com/metacubex/sing-tun v0.4.6-0.20250312042506-6d3b4dc05c04/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0= github.com/metacubex/sing-shadowtls v0.0.0-20250412122235-0e9005731a63/go.mod h1:eDZ2JpkSkewGmUlCoLSn2MRFn1D0jKPIys/6aogFx7U=
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5 h1:hcsz5e5lqhBxn3iQQDIF60FLZ8PQT542GTQZ+1wcIGo=
github.com/metacubex/sing-tun v0.4.6-0.20250412144348-c426cb167db5/go.mod h1:V0N4rr0dWPBEE20ESkTXdbtx2riQYcb6YtwC5w/9wl0=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8= github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82 h1:zZp5uct9+/0Hb1jKGyqDjCU4/72t43rs7qOq3Rc9oU8=
github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ= github.com/metacubex/sing-vmess v0.1.14-0.20250228002636-abc39e113b82/go.mod h1:nE7Mdzj/QUDwgRi/8BASPtsxtIFZTHA4Yst5GgwbGCQ=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589 h1:Z6bNy0HLTjx6BKIkV48sV/yia/GP8Bnyb5JQuGgSGzg=
github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc= github.com/metacubex/sing-wireguard v0.0.0-20241126021510-0827d417b589/go.mod h1:4NclTLIZuk+QkHVCGrP87rHi/y8YjgPytxTgApJNMhc=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY=
github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw=
github.com/metacubex/utls v1.6.8-alpha.4 h1:5EvsCHxDNneaOtAyc8CztoNSpmonLvkvuGs01lIeeEI= github.com/metacubex/utls v1.7.0-alpha.1 h1:oMFsPh2oTlALJ7vKXPJuqgy0YeiZ+q/LLw+ZdxZ80l4=
github.com/metacubex/utls v1.6.8-alpha.4/go.mod h1:MEZ5WO/VLKYs/s/dOzEK/mlXOQxc04ESeLzRgjmLYtk= github.com/metacubex/utls v1.7.0-alpha.1/go.mod h1:oknYT0qTOwE4hjPmZOEpzVdefnW7bAdGLvZcqmk4TLU=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181 h1:hJLQviGySBuaynlCwf/oYgIxbVbGRUIKZCxdya9YrbQ=
github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y= github.com/metacubex/wireguard-go v0.0.0-20240922131502-c182e7471181/go.mod h1:phewKljNYiTVT31Gcif8RiCKnTUOgVWFJjccqYM8s+Y=
github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY=
@@ -174,8 +174,6 @@ github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNen
github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8=
github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo= github.com/sagernet/sing-mux v0.2.1 h1:N/3MHymfnFZRd29tE3TaXwPUVVgKvxhtOkiCMLp9HVo=
github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE= github.com/sagernet/sing-mux v0.2.1/go.mod h1:dm3BWL6NvES9pbib7llpylrq7Gq+LjlzG+0RacdxcyE=
github.com/sagernet/sing-shadowtls v0.1.5 h1:uXxmq/HXh8DIiBGLzpMjCbWnzIAFs+lIxiTOjdgG5qo=
github.com/sagernet/sing-shadowtls v0.1.5/go.mod h1:tvrDPTGLrSM46Wnf7mSr+L8NHvgvF8M4YnJF790rZX4=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ=
github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo=
github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew= github.com/samber/lo v1.49.1 h1:4BIFyVfuQSEpluc7Fua+j1NolZHiEHEpaSEKdsH0tew=

View File

@@ -221,7 +221,7 @@ func updateExperimental(c *config.Experimental) {
if c.QUICGoDisableECN { if c.QUICGoDisableECN {
_ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true)) _ = os.Setenv("QUIC_GO_DISABLE_ECN", strconv.FormatBool(true))
} }
dialer.GetIP4PEnable(c.IP4PEnable) resolver.SetIP4PEnable(c.IP4PEnable)
} }
func updateNTP(c *config.NTP) { func updateNTP(c *config.NTP) {

View File

@@ -86,6 +86,11 @@ func New(config LC.AnyTLSServer, tunnel C.Tunnel, additions ...inbound.Addition)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(tlsConfig.Certificates) > 0 {
l = tls.NewListener(l, tlsConfig)
} else {
return nil, errors.New("disallow using AnyTLS without certificates config")
}
sl.listeners = append(sl.listeners, l) sl.listeners = append(sl.listeners, l)
go func() { go func() {
@@ -130,8 +135,6 @@ func (l *Listener) AddrList() (addrList []net.Addr) {
func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) { func (l *Listener) HandleConn(conn net.Conn, h *sing.ListenerHandler) {
ctx := context.TODO() ctx := context.TODO()
conn = tls.Server(conn, l.tlsConfig)
defer conn.Close() defer conn.Close()
b := buf.NewPacket() b := buf.NewPacket()

View File

@@ -3,9 +3,9 @@ package config
import ( import (
"net/netip" "net/netip"
"github.com/metacubex/mihomo/common/nnip"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
"go4.org/netipx"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
@@ -50,6 +50,10 @@ type Tun struct {
IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"` IncludeUIDRange []string `yaml:"include-uid-range" json:"include-uid-range,omitempty"`
ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"` ExcludeUID []uint32 `yaml:"exclude-uid" json:"exclude-uid,omitempty"`
ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"` ExcludeUIDRange []string `yaml:"exclude-uid-range" json:"exclude-uid-range,omitempty"`
ExcludeSrcPort []uint16 `yaml:"exclude-src-port" json:"exclude-src-port,omitempty"`
ExcludeSrcPortRange []string `yaml:"exclude-src-port-range" json:"exclude-src-port-range,omitempty"`
ExcludeDstPort []uint16 `yaml:"exclude-dst-port" json:"exclude-dst-port,omitempty"`
ExcludeDstPortRange []string `yaml:"exclude-dst-port-range" json:"exclude-dst-port-range,omitempty"`
IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"` IncludeAndroidUser []int `yaml:"include-android-user" json:"include-android-user,omitempty"`
IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"` IncludePackage []string `yaml:"include-package" json:"include-package,omitempty"`
ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"` ExcludePackage []string `yaml:"exclude-package" json:"exclude-package,omitempty"`
@@ -66,11 +70,11 @@ type Tun struct {
func (t *Tun) Sort() { func (t *Tun) Sort() {
slices.Sort(t.DNSHijack) slices.Sort(t.DNSHijack)
slices.SortFunc(t.Inet4Address, nnip.PrefixCompare) slices.SortFunc(t.Inet4Address, netipx.ComparePrefix)
slices.SortFunc(t.Inet6Address, nnip.PrefixCompare) slices.SortFunc(t.Inet6Address, netipx.ComparePrefix)
slices.SortFunc(t.RouteAddress, nnip.PrefixCompare) slices.SortFunc(t.RouteAddress, netipx.ComparePrefix)
slices.Sort(t.RouteAddressSet) slices.Sort(t.RouteAddressSet)
slices.SortFunc(t.RouteExcludeAddress, nnip.PrefixCompare) slices.SortFunc(t.RouteExcludeAddress, netipx.ComparePrefix)
slices.Sort(t.RouteExcludeAddressSet) slices.Sort(t.RouteExcludeAddressSet)
slices.Sort(t.IncludeInterface) slices.Sort(t.IncludeInterface)
slices.Sort(t.ExcludeInterface) slices.Sort(t.ExcludeInterface)
@@ -82,10 +86,10 @@ func (t *Tun) Sort() {
slices.Sort(t.IncludePackage) slices.Sort(t.IncludePackage)
slices.Sort(t.ExcludePackage) slices.Sort(t.ExcludePackage)
slices.SortFunc(t.Inet4RouteAddress, nnip.PrefixCompare) slices.SortFunc(t.Inet4RouteAddress, netipx.ComparePrefix)
slices.SortFunc(t.Inet6RouteAddress, nnip.PrefixCompare) slices.SortFunc(t.Inet6RouteAddress, netipx.ComparePrefix)
slices.SortFunc(t.Inet4RouteExcludeAddress, nnip.PrefixCompare) slices.SortFunc(t.Inet4RouteExcludeAddress, netipx.ComparePrefix)
slices.SortFunc(t.Inet6RouteExcludeAddress, nnip.PrefixCompare) slices.SortFunc(t.Inet6RouteExcludeAddress, netipx.ComparePrefix)
} }
func (t *Tun) Equal(other Tun) bool { func (t *Tun) Equal(other Tun) bool {

View File

@@ -78,7 +78,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }

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

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

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

View 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, &notCloseProxyAdapter{out})
if !assert.NoError(t, err) {
return
}
defer out.Close()
tunnel.DoTest(t, out)
})
}
})
}

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

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

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

View File

@@ -39,6 +39,10 @@ type TunOption struct {
IncludeUIDRange []string `inbound:"include-uid-range,omitempty"` IncludeUIDRange []string `inbound:"include-uid-range,omitempty"`
ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"` ExcludeUID []uint32 `inbound:"exclude-uid,omitempty"`
ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"` ExcludeUIDRange []string `inbound:"exclude-uid-range,omitempty"`
ExcludeSrcPort []uint16 `inbound:"exclude-src-port,omitempty"`
ExcludeSrcPortRange []string `inbound:"exclude-src-port-range,omitempty"`
ExcludeDstPort []uint16 `inbound:"exclude-dst-port,omitempty"`
ExcludeDstPortRange []string `inbound:"exclude-dst-port-range,omitempty"`
IncludeAndroidUser []int `inbound:"include-android-user,omitempty"` IncludeAndroidUser []int `inbound:"include-android-user,omitempty"`
IncludePackage []string `inbound:"include-package,omitempty"` IncludePackage []string `inbound:"include-package,omitempty"`
ExcludePackage []string `inbound:"exclude-package,omitempty"` ExcludePackage []string `inbound:"exclude-package,omitempty"`
@@ -137,6 +141,10 @@ func NewTun(options *TunOption) (*Tun, error) {
IncludeUIDRange: options.IncludeUIDRange, IncludeUIDRange: options.IncludeUIDRange,
ExcludeUID: options.ExcludeUID, ExcludeUID: options.ExcludeUID,
ExcludeUIDRange: options.ExcludeUIDRange, ExcludeUIDRange: options.ExcludeUIDRange,
ExcludeSrcPort: options.ExcludeSrcPort,
ExcludeSrcPortRange: options.ExcludeSrcPortRange,
ExcludeDstPort: options.ExcludeDstPort,
ExcludeDstPortRange: options.ExcludeDstPortRange,
IncludeAndroidUser: options.IncludeAndroidUser, IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage, IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage, ExcludePackage: options.ExcludePackage,

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

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

View File

@@ -3,8 +3,6 @@ package inner
import ( import (
"errors" "errors"
"net" "net"
"net/netip"
"strconv"
N "github.com/metacubex/mihomo/common/net" N "github.com/metacubex/mihomo/common/net"
C "github.com/metacubex/mihomo/constant" C "github.com/metacubex/mihomo/constant"
@@ -16,9 +14,13 @@ func New(t C.Tunnel) {
tunnel = t tunnel = t
} }
func HandleTcp(address string, proxy string) (conn net.Conn, err error) { func GetTunnel() C.Tunnel {
return tunnel
}
func HandleTcp(tunnel C.Tunnel, address string, proxy string) (conn net.Conn, err error) {
if tunnel == nil { if tunnel == nil {
return nil, errors.New("tcp uninitialized") return nil, errors.New("tunnel uninitialized")
} }
// executor Parsed // executor Parsed
conn1, conn2 := N.Pipe() conn1, conn2 := N.Pipe()
@@ -31,15 +33,8 @@ func HandleTcp(address string, proxy string) (conn net.Conn, err error) {
if proxy != "" { if proxy != "" {
metadata.SpecialProxy = proxy metadata.SpecialProxy = proxy
} }
if h, port, err := net.SplitHostPort(address); err == nil { if err = metadata.SetRemoteAddress(address); err != nil {
if port, err := strconv.ParseUint(port, 10, 16); err == nil { return nil, err
metadata.DstPort = uint16(port)
}
if ip, err := netip.ParseAddr(h); err == nil {
metadata.DstIP = ip
} else {
metadata.Host = h
}
} }
go tunnel.HandleTCPConn(conn2, metadata) go tunnel.HandleTCPConn(conn2, metadata)

View File

@@ -73,7 +73,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -9,13 +9,15 @@ import (
"net" "net"
"time" "time"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/listener/inner" "github.com/metacubex/mihomo/listener/inner"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp" "github.com/metacubex/mihomo/ntp"
"github.com/metacubex/reality" utls "github.com/metacubex/utls"
) )
type Conn = reality.Conn type Conn = utls.Conn
type Config struct { type Config struct {
Dest string Dest string
@@ -26,13 +28,14 @@ type Config struct {
Proxy string Proxy string
} }
func (c Config) Build() (*Builder, error) { func (c Config) Build(tunnel C.Tunnel) (*Builder, error) {
realityConfig := &reality.Config{} realityConfig := &utls.RealityConfig{}
realityConfig.SessionTicketsDisabled = true realityConfig.SessionTicketsDisabled = true
realityConfig.Type = "tcp" realityConfig.Type = "tcp"
realityConfig.Dest = c.Dest realityConfig.Dest = c.Dest
realityConfig.Time = ntp.Now realityConfig.Time = ntp.Now
realityConfig.ServerNames = make(map[string]bool) realityConfig.ServerNames = make(map[string]bool)
realityConfig.Log = log.Debugln
for _, it := range c.ServerNames { for _, it := range c.ServerNames {
realityConfig.ServerNames[it] = true realityConfig.ServerNames[it] = true
} }
@@ -50,7 +53,11 @@ func (c Config) Build() (*Builder, error) {
realityConfig.ShortIds = make(map[[8]byte]bool) realityConfig.ShortIds = make(map[[8]byte]bool)
for i, shortIDString := range c.ShortID { for i, shortIDString := range c.ShortID {
var shortID [8]byte var shortID [8]byte
decodedLen, err := hex.Decode(shortID[:], []byte(shortIDString)) decodedLen := hex.DecodedLen(len(shortIDString))
if decodedLen > 8 {
return nil, fmt.Errorf("invalid short_id[%d]: %s", i, shortIDString)
}
decodedLen, err = hex.Decode(shortID[:], []byte(shortIDString))
if err != nil { if err != nil {
return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err) return nil, fmt.Errorf("decode short_id[%d] '%s': %w", i, shortIDString, err)
} }
@@ -61,18 +68,18 @@ func (c Config) Build() (*Builder, error) {
} }
realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) { realityConfig.DialContext = func(ctx context.Context, network, address string) (net.Conn, error) {
return inner.HandleTcp(address, c.Proxy) return inner.HandleTcp(tunnel, address, c.Proxy)
} }
return &Builder{realityConfig}, nil return &Builder{realityConfig}, nil
} }
type Builder struct { type Builder struct {
realityConfig *reality.Config realityConfig *utls.RealityConfig
} }
func (b Builder) NewListener(l net.Listener) net.Listener { func (b Builder) NewListener(l net.Listener) net.Listener {
l = reality.NewListener(l, b.realityConfig) l = utls.NewRealityListener(l, b.realityConfig)
// Due to low implementation quality, the reality server intercepted half close and caused memory leaks. // Due to low implementation quality, the reality server intercepted half close and caused memory leaks.
// We fixed it by calling Close() directly. // We fixed it by calling Close() directly.
l = realityListenerWrapper{l} l = realityListenerWrapper{l}
@@ -80,7 +87,7 @@ func (b Builder) NewListener(l net.Listener) net.Listener {
} }
type realityConnWrapper struct { type realityConnWrapper struct {
*reality.Conn *utls.Conn
} }
func (c realityConnWrapper) Upstream() any { func (c realityConnWrapper) Upstream() any {
@@ -100,5 +107,5 @@ func (l realityListenerWrapper) Accept() (net.Conn, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return realityConnWrapper{c.(*reality.Conn)}, nil return realityConnWrapper{c.(*utls.Conn)}, nil
} }

View File

@@ -72,7 +72,7 @@ func NewListenerHandler(lc ListenerConfig) (h *ListenerHandler, err error) {
NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context { NewStreamContext: func(ctx context.Context, conn net.Conn) context.Context {
return ctx return ctx
}, },
Logger: log.SingLogger, Logger: log.SingInfoToDebugLogger, // convert sing-mux info log to debug
Handler: h, Handler: h,
Padding: lc.MuxOption.Padding, Padding: lc.MuxOption.Padding,
Brutal: mux.BrutalOptions{ Brutal: mux.BrutalOptions{

View File

@@ -196,7 +196,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
ctx := sing.WithAdditions(context.TODO(), additions...) ctx := sing.WithAdditions(context.TODO(), additions...)
err := l.service.NewConnection(ctx, conn, M.Metadata{ err := l.service.NewConnection(ctx, conn, M.Metadata{
Protocol: "shadowsocks", Protocol: "shadowsocks",
Source: M.ParseSocksaddr(conn.RemoteAddr().String()), Source: M.SocksaddrFromNet(conn.RemoteAddr()),
}) })
if err != nil { if err != nil {
_ = conn.Close() _ = conn.Close()

View File

@@ -21,6 +21,7 @@ import (
LC "github.com/metacubex/mihomo/listener/config" LC "github.com/metacubex/mihomo/listener/config"
"github.com/metacubex/mihomo/listener/sing" "github.com/metacubex/mihomo/listener/sing"
"github.com/metacubex/mihomo/log" "github.com/metacubex/mihomo/log"
"golang.org/x/exp/constraints"
tun "github.com/metacubex/sing-tun" tun "github.com/metacubex/sing-tun"
"github.com/metacubex/sing-tun/control" "github.com/metacubex/sing-tun/control"
@@ -211,6 +212,22 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
return nil, E.Cause(err, "parse exclude_uid_range") return nil, E.Cause(err, "parse exclude_uid_range")
} }
} }
excludeSrcPort := uidToRange(options.ExcludeSrcPort)
if len(options.ExcludeSrcPortRange) > 0 {
var err error
excludeSrcPort, err = parseRange(excludeSrcPort, options.ExcludeSrcPortRange)
if err != nil {
return nil, E.Cause(err, "parse exclude_src_port_range")
}
}
excludeDstPort := uidToRange(options.ExcludeDstPort)
if len(options.ExcludeDstPortRange) > 0 {
var err error
excludeDstPort, err = parseRange(excludeDstPort, options.ExcludeDstPortRange)
if err != nil {
return nil, E.Cause(err, "parse exclude_dst_port_range")
}
}
var dnsAdds []netip.AddrPort var dnsAdds []netip.AddrPort
@@ -339,6 +356,8 @@ func New(options LC.Tun, tunnel C.Tunnel, additions ...inbound.Addition) (l *Lis
ExcludeInterface: options.ExcludeInterface, ExcludeInterface: options.ExcludeInterface,
IncludeUID: includeUID, IncludeUID: includeUID,
ExcludeUID: excludeUID, ExcludeUID: excludeUID,
ExcludeSrcPort: excludeSrcPort,
ExcludeDstPort: excludeDstPort,
IncludeAndroidUser: options.IncludeAndroidUser, IncludeAndroidUser: options.IncludeAndroidUser,
IncludePackage: options.IncludePackage, IncludePackage: options.IncludePackage,
ExcludePackage: options.ExcludePackage, ExcludePackage: options.ExcludePackage,
@@ -566,13 +585,13 @@ func (d *cDialerInterfaceFinder) FindInterfaceName(destination netip.Addr) strin
return "<invalid>" return "<invalid>"
} }
func uidToRange(uidList []uint32) []ranges.Range[uint32] { func uidToRange[T constraints.Integer](uidList []T) []ranges.Range[T] {
return common.Map(uidList, func(uid uint32) ranges.Range[uint32] { return common.Map(uidList, func(uid T) ranges.Range[T] {
return ranges.NewSingle(uid) return ranges.NewSingle(uid)
}) })
} }
func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.Range[uint32], error) { func parseRange[T constraints.Integer](uidRanges []ranges.Range[T], rangeList []string) ([]ranges.Range[T], error) {
for _, uidRange := range rangeList { for _, uidRange := range rangeList {
if !strings.Contains(uidRange, ":") { if !strings.Contains(uidRange, ":") {
return nil, E.New("missing ':' in range: ", uidRange) return nil, E.New("missing ':' in range: ", uidRange)
@@ -593,7 +612,7 @@ func parseRange(uidRanges []ranges.Range[uint32], rangeList []string) ([]ranges.
if err != nil { if err != nil {
return nil, E.Cause(err, "parse range end") return nil, E.Cause(err, "parse range end")
} }
uidRanges = append(uidRanges, ranges.New(uint32(start), uint32(end))) uidRanges = append(uidRanges, ranges.New(T(start), T(end)))
} }
return uidRanges, nil return uidRanges, nil
} }

View File

@@ -22,14 +22,13 @@ import (
mihomoVMess "github.com/metacubex/mihomo/transport/vmess" mihomoVMess "github.com/metacubex/mihomo/transport/vmess"
"github.com/metacubex/sing-vmess/vless" "github.com/metacubex/sing-vmess/vless"
utls "github.com/metacubex/utls"
"github.com/sagernet/sing/common" "github.com/sagernet/sing/common"
"github.com/sagernet/sing/common/metadata" "github.com/sagernet/sing/common/metadata"
) )
func init() { func init() {
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*reality.Conn](conn) tlsConn, loaded := common.Cast[*reality.Conn](conn) // *utls.Conn
if !loaded { if !loaded {
return return
} }
@@ -37,15 +36,7 @@ func init() {
}) })
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) { vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*utls.UConn](conn) tlsConn, loaded := common.Cast[*tlsC.UConn](conn) // *utls.UConn
if !loaded {
return
}
return true, tlsConn.NetConn(), reflect.TypeOf(tlsConn.Conn).Elem(), unsafe.Pointer(tlsConn.Conn)
})
vless.RegisterTLS(func(conn net.Conn) (loaded bool, netConn net.Conn, reflectType reflect.Type, reflectPointer unsafe.Pointer) {
tlsConn, loaded := common.Cast[*tlsC.UConn](conn)
if !loaded { if !loaded {
return return
} }
@@ -106,7 +97,7 @@ func New(config LC.VlessServer, tunnel C.Tunnel, additions ...inbound.Addition)
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -201,7 +192,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
ctx := sing.WithAdditions(context.TODO(), additions...) ctx := sing.WithAdditions(context.TODO(), additions...)
err := l.service.NewConnection(ctx, conn, metadata.Metadata{ err := l.service.NewConnection(ctx, conn, metadata.Metadata{
Protocol: "vless", Protocol: "vless",
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()), Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
}) })
if err != nil { if err != nil {
_ = conn.Close() _ = conn.Close()

View File

@@ -90,7 +90,7 @@ func New(config LC.VmessServer, tunnel C.Tunnel, additions ...inbound.Addition)
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -187,7 +187,7 @@ func (l *Listener) HandleConn(conn net.Conn, tunnel C.Tunnel, additions ...inbou
ctx := sing.WithAdditions(context.TODO(), additions...) ctx := sing.WithAdditions(context.TODO(), additions...)
err := l.service.NewConnection(ctx, conn, metadata.Metadata{ err := l.service.NewConnection(ctx, conn, metadata.Metadata{
Protocol: "vmess", Protocol: "vmess",
Source: metadata.ParseSocksaddr(conn.RemoteAddr().String()), Source: metadata.SocksaddrFromNet(conn.RemoteAddr()),
}) })
if err != nil { if err != nil {
_ = conn.Close() _ = conn.Close()

View File

@@ -72,7 +72,7 @@ func NewWithConfig(config LC.AuthServer, tunnel C.Tunnel, additions ...inbound.A
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -81,7 +81,7 @@ func NewUDP(addr string, tunnel C.Tunnel, additions ...inbound.Addition) (*UDPLi
} }
dscp, _ := getDSCP(oob[:oobn]) dscp, _ := getDSCP(oob[:oobn])
additions = append(additions, inbound.WithDSCP(dscp)) additions := append(additions, inbound.WithDSCP(dscp)) // don't change outside additions
if rAddr.Addr().Is4() { if rAddr.Addr().Is4() {
// try to unmap 4in6 address // try to unmap 4in6 address

View File

@@ -84,7 +84,7 @@ func New(config LC.TrojanServer, tunnel C.Tunnel, additions ...inbound.Addition)
if tlsConfig.Certificates != nil { if tlsConfig.Certificates != nil {
return nil, errors.New("certificate is unavailable in reality") return nil, errors.New("certificate is unavailable in reality")
} }
realityBuilder, err = config.RealityConfig.Build() realityBuilder, err = config.RealityConfig.Build(tunnel)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -60,6 +60,14 @@ func New(config LC.TuicServer, tunnel C.Tunnel, additions ...inbound.Addition) (
} else { } else {
tlsConfig.NextProtos = []string{"h3"} tlsConfig.NextProtos = []string{"h3"}
} }
if config.MaxIdleTime == 0 {
config.MaxIdleTime = 15000
}
if config.AuthenticationTimeout == 0 {
config.AuthenticationTimeout = 1000
}
quicConfig := &quic.Config{ quicConfig := &quic.Config{
MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond, MaxIdleTimeout: time.Duration(config.MaxIdleTime) * time.Millisecond,
MaxIncomingStreams: ServerMaxIncomingStreams, MaxIncomingStreams: ServerMaxIncomingStreams,

View File

@@ -65,4 +65,17 @@ func (l singLogger) Panic(args ...any) {
Fatalln(fmt.Sprint(args...)) Fatalln(fmt.Sprint(args...))
} }
type singInfoToDebugLogger struct {
singLogger
}
func (l singInfoToDebugLogger) InfoContext(ctx context.Context, args ...any) {
Debugln(fmt.Sprint(args...))
}
func (l singInfoToDebugLogger) Info(args ...any) {
Debugln(fmt.Sprint(args...))
}
var SingLogger L.ContextLogger = singLogger{} var SingLogger L.ContextLogger = singLogger{}
var SingInfoToDebugLogger L.ContextLogger = singInfoToDebugLogger{}

View File

@@ -9,7 +9,6 @@ import (
"github.com/metacubex/mihomo/common/atomic" "github.com/metacubex/mihomo/common/atomic"
"github.com/metacubex/mihomo/common/buf" "github.com/metacubex/mihomo/common/buf"
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/anytls/padding" "github.com/metacubex/mihomo/transport/anytls/padding"
"github.com/metacubex/mihomo/transport/anytls/session" "github.com/metacubex/mihomo/transport/anytls/session"
"github.com/metacubex/mihomo/transport/vmess" "github.com/metacubex/mihomo/transport/vmess"
@@ -83,12 +82,7 @@ func (c *Client) CreateOutboundTLSConnection(ctx context.Context) (net.Conn, err
b.WriteZeroN(paddingLen) b.WriteZeroN(paddingLen)
} }
getTlsConn := func() (net.Conn, error) { tlsConn, err := vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
ctx, cancel := context.WithTimeout(ctx, C.DefaultTLSTimeout)
defer cancel()
return vmess.StreamTLSConn(ctx, conn, c.tlsConfig)
}
tlsConn, err := getTlsConn()
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, err return nil, err

View File

@@ -40,7 +40,7 @@ func newStream(id uint32, sess *Session) *Stream {
// Read implements net.Conn // Read implements net.Conn
func (s *Stream) Read(b []byte) (n int, err error) { func (s *Stream) Read(b []byte) (n int, err error) {
n, err = s.pipeR.Read(b) n, err = s.pipeR.Read(b)
if s.dieErr != nil { if n == 0 && s.dieErr != nil {
err = s.dieErr err = s.dieErr
} }
return return

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