feat: support tcp concurrent, Separate dialing and dns resolver ipv6

tcp-concurrent:true
This commit is contained in:
Skyxim 2022-04-23 00:27:22 +08:00
parent 2e1d9a4f2e
commit 81b5543b0d
6 changed files with 211 additions and 19 deletions

View File

@ -3,12 +3,15 @@ package dialer
import ( import (
"context" "context"
"errors" "errors"
"github.com/Dreamacro/clash/log"
"net" "net"
"net/netip" "net/netip"
"github.com/Dreamacro/clash/component/resolver" "github.com/Dreamacro/clash/component/resolver"
) )
var DisableIPv6 = false
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 := &option{ opt := &option{
interfaceName: DefaultInterface.Load(), interfaceName: DefaultInterface.Load(),
@ -51,7 +54,11 @@ func DialContext(ctx context.Context, network, address string, options ...Option
return dialContext(ctx, network, ip, port, opt) return dialContext(ctx, network, ip, port, opt)
case "tcp", "udp": case "tcp", "udp":
return dualStackDialContext(ctx, network, address, opt) if TCPConcurrent && network == "tcp" {
return concurrentDialContext(ctx, network, address, opt)
} else {
return dualStackDialContext(ctx, network, address, opt)
}
default: default:
return nil, errors.New("network invalid") return nil, errors.New("network invalid")
} }
@ -183,3 +190,73 @@ func dualStackDialContext(ctx context.Context, network, address string, opt *opt
return nil, errors.New("never touched") return nil, errors.New("never touched")
} }
func concurrentDialContext(ctx context.Context, network, address string, opt *option) (net.Conn, error) {
host, port, err := net.SplitHostPort(address)
if err != nil {
return nil, err
}
returned := make(chan struct{})
defer close(returned)
type dialResult struct {
ip netip.Addr
net.Conn
error
resolved bool
}
results := make(chan dialResult)
var ips []netip.Addr
if opt.direct {
ips, err = resolver.ResolveAllIP(host)
} else {
ips, err = resolver.ResolveAllIPProxyServerHost(host)
}
tcpRacer := func(ctx context.Context, ip netip.Addr) {
result := dialResult{ip: ip}
defer func() {
select {
case results <- result:
case <-returned:
if result.Conn != nil {
result.Conn.Close()
}
}
}()
v := "4"
if ip.Is6() {
v = "6"
}
log.Debugln("[%s] try use [%s] connected", host, ip.String())
result.Conn, result.error = dialContext(ctx, network+v, ip, port, opt)
}
for _, ip := range ips {
go tcpRacer(ctx, ip)
}
connCount := len(ips)
for res := range results {
connCount--
if res.error == nil {
connIp := res.Conn.RemoteAddr()
log.Debugln("[%s] used [%s] connected", host, connIp)
return res.Conn, nil
}
log.Errorln("connect error:%v", res.error)
if connCount == 0 {
log.Errorln("connect [%s] all ip failed", host)
break
}
}
return nil, errors.New("all ip tcp shakeHands failed")
}

View File

@ -6,6 +6,7 @@ var (
DefaultOptions []Option DefaultOptions []Option
DefaultInterface = atomic.NewString("") DefaultInterface = atomic.NewString("")
DefaultRoutingMark = atomic.NewInt32(0) DefaultRoutingMark = atomic.NewInt32(0)
TCPConcurrent = false
) )
type option struct { type option struct {

View File

@ -40,6 +40,10 @@ type Resolver interface {
ResolveIP(host string) (ip netip.Addr, err error) ResolveIP(host string) (ip netip.Addr, err error)
ResolveIPv4(host string) (ip netip.Addr, err error) ResolveIPv4(host string) (ip netip.Addr, err error)
ResolveIPv6(host string) (ip netip.Addr, err error) ResolveIPv6(host string) (ip netip.Addr, err error)
ResolveAllIP(host string) (ip []netip.Addr, err error)
ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error)
ResolveAllIPv4(host string) (ips []netip.Addr, err error)
ResolveAllIPv6(host string) (ips []netip.Addr, err error)
} }
// ResolveIPv4 with a host, return ipv4 // ResolveIPv4 with a host, return ipv4
@ -191,3 +195,51 @@ func ResolveProxyServerHost(host string) (netip.Addr, error) {
} }
return ResolveIP(host) return ResolveIP(host)
} }
func ResolveAllIPv6WithResolver(host string, r Resolver) ([]netip.Addr, error) {
return r.ResolveAllIPv6(host)
}
func ResolveAllIPv4WithResolver(host string, r Resolver) ([]netip.Addr, error) {
return r.ResolveAllIPv4(host)
}
func ResolveAllIPWithResolver(host string, r Resolver) ([]netip.Addr, error) {
return r.ResolveAllIP(host)
}
func ResolveAllIP(host string) ([]netip.Addr, error) {
return ResolveAllIPWithResolver(host, DefaultResolver)
}
func ResolveAllIPv4(host string) ([]netip.Addr, error) {
return ResolveAllIPv4WithResolver(host, DefaultResolver)
}
func ResolveAllIPv6(host string) ([]netip.Addr, error) {
return ResolveAllIPv6WithResolver(host, DefaultResolver)
}
func ResolveAllIPv6ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPv6WithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIPv6(host)
}
func ResolveAllIPv4ProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPv4WithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIPv4(host)
}
func ResolveAllIPProxyServerHost(host string) ([]netip.Addr, error) {
if ProxyServerHostResolver != nil {
return ResolveAllIPWithResolver(host, ProxyServerHostResolver)
}
return ResolveAllIP(host)
}

View File

@ -49,6 +49,7 @@ type General struct {
RoutingMark int `json:"-"` RoutingMark int `json:"-"`
GeodataMode bool `json:"geodata-mode"` GeodataMode bool `json:"geodata-mode"`
GeodataLoader string `json:"geodata-loader"` GeodataLoader string `json:"geodata-loader"`
TCPConcurrent bool `json:"tcp-concurrent"`
} }
// Inbound config // Inbound config
@ -206,6 +207,7 @@ type RawConfig struct {
RoutingMark int `yaml:"routing-mark"` RoutingMark int `yaml:"routing-mark"`
GeodataMode bool `yaml:"geodata-mode"` GeodataMode bool `yaml:"geodata-mode"`
GeodataLoader string `yaml:"geodata-loader"` GeodataLoader string `yaml:"geodata-loader"`
TCPConcurrent bool `yaml:"tc-pconcurrent" json:"tc-pconcurrent"`
Sniffer SnifferRaw `yaml:"sniffer"` Sniffer SnifferRaw `yaml:"sniffer"`
ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"`
@ -256,6 +258,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) {
Rule: []string{}, Rule: []string{},
Proxy: []map[string]any{}, Proxy: []map[string]any{},
ProxyGroup: []map[string]any{}, ProxyGroup: []map[string]any{},
TCPConcurrent: false,
Tun: RawTun{ Tun: RawTun{
Enable: false, Enable: false,
Device: "", Device: "",
@ -412,6 +415,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) {
RoutingMark: cfg.RoutingMark, RoutingMark: cfg.RoutingMark,
GeodataMode: cfg.GeodataMode, GeodataMode: cfg.GeodataMode,
GeodataLoader: cfg.GeodataLoader, GeodataLoader: cfg.GeodataLoader,
TCPConcurrent: cfg.TCPConcurrent,
}, nil }, nil
} }

View File

@ -44,9 +44,8 @@ type Resolver struct {
proxyServer []dnsClient proxyServer []dnsClient
} }
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA func (r *Resolver) ResolveAllIPPrimaryIPv4(host string) (ips []netip.Addr, err error) {
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) { ch := make(chan []netip.Addr, 1)
ch := make(chan netip.Addr, 1)
go func() { go func() {
defer close(ch) defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA) ip, err := r.resolveIP(host, D.TypeAAAA)
@ -56,27 +55,75 @@ func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
ch <- ip ch <- ip
}() }()
ip, err = r.resolveIP(host, D.TypeA) ips, err = r.resolveIP(host, D.TypeA)
if err == nil { if err == nil {
return return
} }
ip, open := <-ch ip, open := <-ch
if !open { if !open {
return netip.Addr{}, resolver.ErrIPNotFound return nil, resolver.ErrIPNotFound
} }
return ip, nil return ip, nil
} }
func (r *Resolver) ResolveAllIP(host string) (ips []netip.Addr, err error) {
ch := make(chan []netip.Addr, 1)
go func() {
defer close(ch)
ip, err := r.resolveIP(host, D.TypeAAAA)
if err != nil {
return
}
ch <- ip
}()
ips, err = r.resolveIP(host, D.TypeA)
ipv6s, open := <-ch
if !open && err != nil {
return nil, resolver.ErrIPNotFound
}
ips = append(ips, ipv6s...)
return ips, nil
}
func (r *Resolver) ResolveAllIPv4(host string) (ips []netip.Addr, err error) {
return r.resolveIP(host, D.TypeA)
}
func (r *Resolver) ResolveAllIPv6(host string) (ips []netip.Addr, err error) {
return r.resolveIP(host, D.TypeAAAA)
}
// ResolveIP request with TypeA and TypeAAAA, priority return TypeA
func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) {
if ips, err := r.ResolveAllIPPrimaryIPv4(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
}
// ResolveIPv4 request with TypeA // ResolveIPv4 request with TypeA
func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv4(host string) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeA) if ips, err := r.ResolveAllIPv4(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
} }
// ResolveIPv6 request with TypeAAAA // ResolveIPv6 request with TypeAAAA
func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) { func (r *Resolver) ResolveIPv6(host string) (ip netip.Addr, err error) {
return r.resolveIP(host, D.TypeAAAA) if ips, err := r.ResolveAllIPv6(host); err == nil {
return ips[rand.Intn(len(ips))], nil
} else {
return netip.Addr{}, err
}
} }
func (r *Resolver) shouldIPFallback(ip netip.Addr) bool { func (r *Resolver) shouldIPFallback(ip netip.Addr) bool {
@ -254,16 +301,16 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er
return return
} }
func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) { func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err error) {
ip, err = netip.ParseAddr(host) ip, err := netip.ParseAddr(host)
if err == nil { if err == nil {
isIPv4 := ip.Is4() isIPv4 := ip.Is4()
if dnsType == D.TypeAAAA && !isIPv4 { if dnsType == D.TypeAAAA && !isIPv4 {
return ip, nil return []netip.Addr{ip}, nil
} else if dnsType == D.TypeA && isIPv4 { } else if dnsType == D.TypeA && isIPv4 {
return ip, nil return []netip.Addr{ip}, nil
} else { } else {
return netip.Addr{}, resolver.ErrIPVersion return []netip.Addr{}, resolver.ErrIPVersion
} }
} }
@ -272,16 +319,15 @@ func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err er
msg, err := r.Exchange(query) msg, err := r.Exchange(query)
if err != nil { if err != nil {
return netip.Addr{}, err return []netip.Addr{}, err
} }
ips := msgToIP(msg) ips = msgToIP(msg)
ipLength := len(ips) ipLength := len(ips)
if ipLength == 0 { if ipLength == 0 {
return netip.Addr{}, resolver.ErrIPNotFound return []netip.Addr{}, resolver.ErrIPNotFound
} }
ip = ips[rand.Intn(ipLength)]
return return
} }

View File

@ -139,6 +139,8 @@ func updateDNS(c *config.DNS, t *config.Tun) {
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
} }
resolver.DisableIPv6 = cfg.IPv6
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)
pr := dns.NewProxyServerHostResolver(r) pr := dns.NewProxyServerHostResolver(r)
m := dns.NewEnhancer(cfg) m := dns.NewEnhancer(cfg)
@ -157,6 +159,7 @@ func updateDNS(c *config.DNS, t *config.Tun) {
if t.Enable { if t.Enable {
resolver.DefaultLocalServer = dns.NewLocalServer(r, m) resolver.DefaultLocalServer = dns.NewLocalServer(r, m)
log.Infoln("DNS enable IPv6 resolve")
} }
if c.Enable { if c.Enable {
@ -243,10 +246,19 @@ func updateSniffer(sniffer *config.Sniffer) {
func updateGeneral(general *config.General, force bool) { func updateGeneral(general *config.General, force bool) {
log.SetLevel(general.LogLevel) log.SetLevel(general.LogLevel)
tunnel.SetMode(general.Mode) tunnel.SetMode(general.Mode)
resolver.DisableIPv6 = !general.IPv6 dialer.DisableIPv6 = general.IPv6
if !dialer.DisableIPv6 {
resolver.DisableIPv6 = dialer.DisableIPv6
} else {
log.Infoln("Use IPv6")
}
dialer.TCPConcurrent = general.TCPConcurrent
if dialer.TCPConcurrent {
log.Infoln("Use tcp concurrent")
}
adapter.UnifiedDelay.Store(general.UnifiedDelay) adapter.UnifiedDelay.Store(general.UnifiedDelay)
dialer.DefaultInterface.Store(general.Interface) dialer.DefaultInterface.Store(general.Interface)
if dialer.DefaultInterface.Load() != "" { if dialer.DefaultInterface.Load() != "" {