From 81b5543b0d7712839c20dbb12376ab668192c554 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Sat, 23 Apr 2022 00:27:22 +0800 Subject: [PATCH] feat: support tcp concurrent, Separate dialing and dns resolver ipv6 tcp-concurrent:true --- component/dialer/dialer.go | 79 +++++++++++++++++++++++++++++++++- component/dialer/options.go | 1 + component/resolver/resolver.go | 52 ++++++++++++++++++++++ config/config.go | 4 ++ dns/resolver.go | 78 ++++++++++++++++++++++++++------- hub/executor/executor.go | 16 ++++++- 6 files changed, 211 insertions(+), 19 deletions(-) diff --git a/component/dialer/dialer.go b/component/dialer/dialer.go index 16e6eed1..663568ad 100644 --- a/component/dialer/dialer.go +++ b/component/dialer/dialer.go @@ -3,12 +3,15 @@ package dialer import ( "context" "errors" + "github.com/Dreamacro/clash/log" "net" "net/netip" "github.com/Dreamacro/clash/component/resolver" ) +var DisableIPv6 = false + func DialContext(ctx context.Context, network, address string, options ...Option) (net.Conn, error) { opt := &option{ interfaceName: DefaultInterface.Load(), @@ -51,7 +54,11 @@ func DialContext(ctx context.Context, network, address string, options ...Option return dialContext(ctx, network, ip, port, opt) 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: 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") } + +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") +} diff --git a/component/dialer/options.go b/component/dialer/options.go index 2985dc7b..bde81a09 100644 --- a/component/dialer/options.go +++ b/component/dialer/options.go @@ -6,6 +6,7 @@ var ( DefaultOptions []Option DefaultInterface = atomic.NewString("") DefaultRoutingMark = atomic.NewInt32(0) + TCPConcurrent = false ) type option struct { diff --git a/component/resolver/resolver.go b/component/resolver/resolver.go index 7fe625c1..841e8af9 100644 --- a/component/resolver/resolver.go +++ b/component/resolver/resolver.go @@ -40,6 +40,10 @@ type Resolver interface { ResolveIP(host string) (ip netip.Addr, err error) ResolveIPv4(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 @@ -191,3 +195,51 @@ func ResolveProxyServerHost(host string) (netip.Addr, error) { } 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) +} diff --git a/config/config.go b/config/config.go index 168d37a0..acf8e5a4 100644 --- a/config/config.go +++ b/config/config.go @@ -49,6 +49,7 @@ type General struct { RoutingMark int `json:"-"` GeodataMode bool `json:"geodata-mode"` GeodataLoader string `json:"geodata-loader"` + TCPConcurrent bool `json:"tcp-concurrent"` } // Inbound config @@ -206,6 +207,7 @@ type RawConfig struct { RoutingMark int `yaml:"routing-mark"` GeodataMode bool `yaml:"geodata-mode"` GeodataLoader string `yaml:"geodata-loader"` + TCPConcurrent bool `yaml:"tc-pconcurrent" json:"tc-pconcurrent"` Sniffer SnifferRaw `yaml:"sniffer"` ProxyProvider map[string]map[string]any `yaml:"proxy-providers"` @@ -256,6 +258,7 @@ func UnmarshalRawConfig(buf []byte) (*RawConfig, error) { Rule: []string{}, Proxy: []map[string]any{}, ProxyGroup: []map[string]any{}, + TCPConcurrent: false, Tun: RawTun{ Enable: false, Device: "", @@ -412,6 +415,7 @@ func parseGeneral(cfg *RawConfig) (*General, error) { RoutingMark: cfg.RoutingMark, GeodataMode: cfg.GeodataMode, GeodataLoader: cfg.GeodataLoader, + TCPConcurrent: cfg.TCPConcurrent, }, nil } diff --git a/dns/resolver.go b/dns/resolver.go index da4fb1e4..369cb9f8 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -44,9 +44,8 @@ type Resolver struct { proxyServer []dnsClient } -// ResolveIP request with TypeA and TypeAAAA, priority return TypeA -func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) { - ch := make(chan netip.Addr, 1) +func (r *Resolver) ResolveAllIPPrimaryIPv4(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) @@ -56,27 +55,75 @@ func (r *Resolver) ResolveIP(host string) (ip netip.Addr, err error) { ch <- ip }() - ip, err = r.resolveIP(host, D.TypeA) + ips, err = r.resolveIP(host, D.TypeA) if err == nil { return } ip, open := <-ch if !open { - return netip.Addr{}, resolver.ErrIPNotFound + return nil, resolver.ErrIPNotFound } 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 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 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 { @@ -254,16 +301,16 @@ func (r *Resolver) ipExchange(ctx context.Context, m *D.Msg) (msg *D.Msg, err er return } -func (r *Resolver) resolveIP(host string, dnsType uint16) (ip netip.Addr, err error) { - ip, err = netip.ParseAddr(host) +func (r *Resolver) resolveIP(host string, dnsType uint16) (ips []netip.Addr, err error) { + ip, err := netip.ParseAddr(host) if err == nil { isIPv4 := ip.Is4() if dnsType == D.TypeAAAA && !isIPv4 { - return ip, nil + return []netip.Addr{ip}, nil } else if dnsType == D.TypeA && isIPv4 { - return ip, nil + return []netip.Addr{ip}, nil } 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) if err != nil { - return netip.Addr{}, err + return []netip.Addr{}, err } - ips := msgToIP(msg) + ips = msgToIP(msg) ipLength := len(ips) if ipLength == 0 { - return netip.Addr{}, resolver.ErrIPNotFound + return []netip.Addr{}, resolver.ErrIPNotFound } - ip = ips[rand.Intn(ipLength)] return } diff --git a/hub/executor/executor.go b/hub/executor/executor.go index e70aac3d..1e15c08e 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -139,6 +139,8 @@ func updateDNS(c *config.DNS, t *config.Tun) { ProxyServer: c.ProxyServerNameserver, } + resolver.DisableIPv6 = cfg.IPv6 + r := dns.NewResolver(cfg) pr := dns.NewProxyServerHostResolver(r) m := dns.NewEnhancer(cfg) @@ -157,6 +159,7 @@ func updateDNS(c *config.DNS, t *config.Tun) { if t.Enable { resolver.DefaultLocalServer = dns.NewLocalServer(r, m) + log.Infoln("DNS enable IPv6 resolve") } if c.Enable { @@ -243,10 +246,19 @@ func updateSniffer(sniffer *config.Sniffer) { func updateGeneral(general *config.General, force bool) { log.SetLevel(general.LogLevel) 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) - dialer.DefaultInterface.Store(general.Interface) if dialer.DefaultInterface.Load() != "" {