From 3e424dea7b1720e91edb729e642efa7ccd14c455 Mon Sep 17 00:00:00 2001 From: Skyxim Date: Thu, 21 Jul 2022 14:03:49 +0800 Subject: [PATCH] refactor: DoH use fragment setting params --- config/config.go | 20 +++++- dns/doh.go | 151 +++++++++++++++------------------------ dns/resolver.go | 12 ++-- dns/util.go | 4 +- docs/config.yaml | 7 +- hub/executor/executor.go | 1 - 6 files changed, 86 insertions(+), 109 deletions(-) diff --git a/config/config.go b/config/config.go index da8fb828..1359b0ae 100644 --- a/config/config.go +++ b/config/config.go @@ -664,7 +664,8 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.Error()) } - var addr, dnsNetType string + var addr, dnsNetType, proxyAdapter string + params := map[string]string{} switch u.Scheme { case "udp": addr, err = hostWithDefaultPort(u.Host, "53") @@ -679,6 +680,20 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path} addr = clearURL.String() dnsNetType = "https" // DNS over HTTPS + if len(u.Fragment) != 0 { + for _, s := range strings.Split(u.Fragment, "&") { + arr := strings.Split(s, "=") + if len(arr) == 0 { + continue + } else if len(arr) == 1 { + proxyAdapter = arr[0] + } else if len(arr) == 2 { + params[arr[0]] = arr[1] + } else { + params[arr[0]] = strings.Join(arr[1:], "=") + } + } + } case "dhcp": addr = u.Host dnsNetType = "dhcp" // UDP from DHCP @@ -698,8 +713,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) { dns.NameServer{ Net: dnsNetType, Addr: addr, - ProxyAdapter: u.Fragment, + ProxyAdapter: proxyAdapter, Interface: dialer.DefaultInterface, + Params: params, }, ) } diff --git a/dns/doh.go b/dns/doh.go index 40029c94..8403f7d1 100644 --- a/dns/doh.go +++ b/dns/doh.go @@ -4,15 +4,14 @@ import ( "bytes" "context" "crypto/tls" + "fmt" "github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/resolver" - tls2 "github.com/Dreamacro/clash/component/tls" + tlsC "github.com/Dreamacro/clash/component/tls" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/http3" D "github.com/miekg/dns" - "go.uber.org/atomic" "io" - "io/ioutil" "net" "net/http" "strconv" @@ -85,23 +84,57 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) { return msg, err } -func newDoHClient(url string, r *Resolver, preferH3 bool, proxyAdapter string) *dohClient { - return &dohClient{ - url: url, - transport: newDohTransport(r, preferH3, proxyAdapter), - } -} +func newDoHClient(url string, r *Resolver, params map[string]string, proxyAdapter string) *dohClient { + useH3 := params["h3"] == "true" + TLCConfig := tlsC.GetDefaultTLSConfig() + var transport http.RoundTripper + if useH3 { + transport = &http3.RoundTripper{ + Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { + host, port, err := net.SplitHostPort(addr) + if err != nil { + return nil, err + } -type dohTransport struct { - *http.Transport - h3 *http3.RoundTripper - preferH3 bool - canUseH3 atomic.Bool -} + ip, err := resolver.ResolveIPWithResolver(host, r) + if err != nil { + return nil, err + } -func newDohTransport(r *Resolver, preferH3 bool, proxyAdapter string) *dohTransport { - dohT := &dohTransport{ - Transport: &http.Transport{ + portInt, err := strconv.Atoi(port) + if err != nil { + return nil, err + } + + udpAddr := net.UDPAddr{ + IP: net.ParseIP(ip.String()), + Port: portInt, + } + + var conn net.PacketConn + if proxyAdapter == "" { + conn, err = dialer.ListenPacket(ctx, "udp", "") + if err != nil { + return nil, err + } + } else { + if wrapConn, err := dialContextExtra(ctx, proxyAdapter, "udp", ip, port); err == nil { + if pc, ok := wrapConn.(*wrapPacketConn); ok { + conn = pc + } else { + return nil, fmt.Errorf("conn isn't wrapPacketConn") + } + } else { + return nil, err + } + } + + return quic.DialEarlyContext(ctx, conn, &udpAddr, host, tlsCfg, cfg) + }, + TLSClientConfig: TLCConfig, + } + } else { + transport = &http.Transport{ ForceAttemptHTTP2: true, DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { host, port, err := net.SplitHostPort(addr) @@ -120,84 +153,12 @@ func newDohTransport(r *Resolver, preferH3 bool, proxyAdapter string) *dohTransp return dialContextExtra(ctx, proxyAdapter, "tcp", ip, port) } }, - TLSClientConfig: tls2.GetDefaultTLSConfig(), - }, - preferH3: preferH3, - } - - dohT.canUseH3.Store(preferH3) - if preferH3 { - dohT.h3 = &http3.RoundTripper{ - Dial: func(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) { - host, port, err := net.SplitHostPort(addr) - if err != nil { - return nil, err - } - - ip, err := resolver.ResolveIPWithResolver(host, r) - if err != nil { - return nil, err - } - - if proxyAdapter == "" { - return quic.DialAddrEarlyContext(ctx, net.JoinHostPort(ip.String(), port), tlsCfg, cfg) - } else { - if conn, err := dialContextExtra(ctx, proxyAdapter, "udp", ip, port); err == nil { - portInt, err := strconv.Atoi(port) - if err != nil { - return nil, err - } - - udpAddr := net.UDPAddr{ - IP: net.ParseIP(ip.String()), - Port: portInt, - } - - return quic.DialEarlyContext(ctx, conn.(net.PacketConn), &udpAddr, host, tlsCfg, cfg) - } else { - return nil, err - } - } - }, - TLSClientConfig: tls2.GetDefaultTLSConfig(), + TLSClientConfig: TLCConfig, } } - return dohT -} - -func (doh *dohTransport) RoundTrip(req *http.Request) (*http.Response, error) { - var resp *http.Response - var err error - var bodyBytes []byte - var h3Err bool - var fallbackErr bool - defer func() { - if doh.preferH3 && (h3Err || fallbackErr) { - doh.canUseH3.Store(doh.preferH3 && (!h3Err || fallbackErr)) - } - }() - - if req.Body != nil { - bodyBytes, err = ioutil.ReadAll(req.Body) - } - - req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) - if doh.canUseH3.Load() { - resp, err = doh.h3.RoundTrip(req) - h3Err = err != nil - if !h3Err { - return resp, err - } else { - req.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes)) - } - } - - resp, err = doh.Transport.RoundTrip(req) - fallbackErr = err != nil - if fallbackErr { - return resp, err - } - - return resp, err + return &dohClient{ + url: url, + transport: transport, + } } diff --git a/dns/resolver.go b/dns/resolver.go index 597254c4..b576fe36 100644 --- a/dns/resolver.go +++ b/dns/resolver.go @@ -354,6 +354,7 @@ type NameServer struct { Addr string Interface *atomic.String ProxyAdapter string + Params map[string]string } type FallbackFilter struct { @@ -365,7 +366,6 @@ type FallbackFilter struct { } type Config struct { - PreferH3 bool Main, Fallback []NameServer Default []NameServer ProxyServer []NameServer @@ -379,29 +379,29 @@ type Config struct { func NewResolver(config Config) *Resolver { defaultResolver := &Resolver{ - main: transform(config.Default, nil, config.PreferH3), + main: transform(config.Default, nil), lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), } r := &Resolver{ ipv6: config.IPv6, - main: transform(config.Main, defaultResolver, config.PreferH3), + main: transform(config.Main, defaultResolver), lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)), hosts: config.Hosts, } if len(config.Fallback) != 0 { - r.fallback = transform(config.Fallback, defaultResolver, config.PreferH3) + r.fallback = transform(config.Fallback, defaultResolver) } if len(config.ProxyServer) != 0 { - r.proxyServer = transform(config.ProxyServer, defaultResolver, config.PreferH3) + r.proxyServer = transform(config.ProxyServer, defaultResolver) } if len(config.Policy) != 0 { r.policy = trie.New[*Policy]() for domain, nameserver := range config.Policy { - _ = r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver, config.PreferH3))) + _ = r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver))) } } diff --git a/dns/util.go b/dns/util.go index 280a35df..50d9decd 100644 --- a/dns/util.go +++ b/dns/util.go @@ -54,12 +54,12 @@ func isIPRequest(q D.Question) bool { return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) } -func transform(servers []NameServer, resolver *Resolver, preferH3 bool) []dnsClient { +func transform(servers []NameServer, resolver *Resolver) []dnsClient { ret := []dnsClient{} for _, s := range servers { switch s.Net { case "https": - ret = append(ret, newDoHClient(s.Addr, resolver, preferH3, s.ProxyAdapter)) + ret = append(ret, newDoHClient(s.Addr, resolver, s.Params, s.ProxyAdapter)) continue case "dhcp": ret = append(ret, newDHCPClient(s.Addr)) diff --git a/docs/config.yaml b/docs/config.yaml index 18a5fb79..0c9e2547 100644 --- a/docs/config.yaml +++ b/docs/config.yaml @@ -73,7 +73,6 @@ profile: dns: enable: false # 关闭将使用系统DNS listen: 0.0.0.0:53 # 开启DNS服务器监听 - prefer-h3: false # 启动DOH优先使用http/3 # ipv6: false # false将返回AAAA的空结果 # 用于解析nameserver,fallbacky以及其他DNS服务器配置的,DNS服务域名 @@ -95,13 +94,15 @@ dns: # - localhost.ptlogin2.qq.com # DNS主要域名配置 - # 支持 UDP,TCP,DOT,DOH,DOQ + # 支持 UDP,TCP,DoT,DoH,DoQ # 这部分为主要DNS配置,影响所有直连,确保使用对大陆解析精准的DNS nameserver: - 114.114.114.114 # default value - 8.8.8.8 # default value - tls://223.5.5.5:853 # DNS over TLS - - https://1.1.1.1/dns-query # DNS over HTTPS + - https://doh.pub/dns-query # DNS over HTTPS + - https://dns.alidns.com/dns-query#h3=true # 强制HTTP/3 + - https://mozilla.cloudflare-dns.com/dns-query#DNS&h3=true # 指定策略组和使用HTTP/3 - dhcp://en0 # dns from dhcp - quic://dns.adguard.com:784 # DNS over QUIC # - '8.8.8.8#en0' # 兼容指定DNS出口网卡 diff --git a/hub/executor/executor.go b/hub/executor/executor.go index 6918fd47..ea98364a 100644 --- a/hub/executor/executor.go +++ b/hub/executor/executor.go @@ -172,7 +172,6 @@ func updateDNS(c *config.DNS, generalIPv6 bool) { Default: c.DefaultNameserver, Policy: c.NameServerPolicy, ProxyServer: c.ProxyServerNameserver, - PreferH3: c.PreferH3, } r := dns.NewResolver(cfg)