refactor: DoH use fragment setting params

This commit is contained in:
gVisor bot 2022-07-21 14:03:49 +08:00
parent 91af078580
commit 21d44fa391
6 changed files with 86 additions and 109 deletions

View File

@ -664,7 +664,8 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
return nil, fmt.Errorf("DNS NameServer[%d] format error: %s", idx, err.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 { switch u.Scheme {
case "udp": case "udp":
addr, err = hostWithDefaultPort(u.Host, "53") 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} clearURL := url.URL{Scheme: "https", Host: u.Host, Path: u.Path}
addr = clearURL.String() addr = clearURL.String()
dnsNetType = "https" // DNS over HTTPS 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": case "dhcp":
addr = u.Host addr = u.Host
dnsNetType = "dhcp" // UDP from DHCP dnsNetType = "dhcp" // UDP from DHCP
@ -698,8 +713,9 @@ func parseNameServer(servers []string) ([]dns.NameServer, error) {
dns.NameServer{ dns.NameServer{
Net: dnsNetType, Net: dnsNetType,
Addr: addr, Addr: addr,
ProxyAdapter: u.Fragment, ProxyAdapter: proxyAdapter,
Interface: dialer.DefaultInterface, Interface: dialer.DefaultInterface,
Params: params,
}, },
) )
} }

View File

@ -4,15 +4,14 @@ import (
"bytes" "bytes"
"context" "context"
"crypto/tls" "crypto/tls"
"fmt"
"github.com/Dreamacro/clash/component/dialer" "github.com/Dreamacro/clash/component/dialer"
"github.com/Dreamacro/clash/component/resolver" "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"
"github.com/lucas-clemente/quic-go/http3" "github.com/lucas-clemente/quic-go/http3"
D "github.com/miekg/dns" D "github.com/miekg/dns"
"go.uber.org/atomic"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@ -85,23 +84,57 @@ func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
return msg, err return msg, err
} }
func newDoHClient(url string, r *Resolver, preferH3 bool, proxyAdapter string) *dohClient { func newDoHClient(url string, r *Resolver, params map[string]string, proxyAdapter string) *dohClient {
return &dohClient{ useH3 := params["h3"] == "true"
url: url, TLCConfig := tlsC.GetDefaultTLSConfig()
transport: newDohTransport(r, preferH3, proxyAdapter), 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 { ip, err := resolver.ResolveIPWithResolver(host, r)
*http.Transport if err != nil {
h3 *http3.RoundTripper return nil, err
preferH3 bool }
canUseH3 atomic.Bool
}
func newDohTransport(r *Resolver, preferH3 bool, proxyAdapter string) *dohTransport { portInt, err := strconv.Atoi(port)
dohT := &dohTransport{ if err != nil {
Transport: &http.Transport{ 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, ForceAttemptHTTP2: true,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr) 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) return dialContextExtra(ctx, proxyAdapter, "tcp", ip, port)
} }
}, },
TLSClientConfig: tls2.GetDefaultTLSConfig(), TLSClientConfig: TLCConfig,
},
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(),
} }
} }
return dohT return &dohClient{
} url: url,
transport: transport,
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
} }

View File

@ -354,6 +354,7 @@ type NameServer struct {
Addr string Addr string
Interface *atomic.String Interface *atomic.String
ProxyAdapter string ProxyAdapter string
Params map[string]string
} }
type FallbackFilter struct { type FallbackFilter struct {
@ -365,7 +366,6 @@ type FallbackFilter struct {
} }
type Config struct { type Config struct {
PreferH3 bool
Main, Fallback []NameServer Main, Fallback []NameServer
Default []NameServer Default []NameServer
ProxyServer []NameServer ProxyServer []NameServer
@ -379,29 +379,29 @@ type Config struct {
func NewResolver(config Config) *Resolver { func NewResolver(config Config) *Resolver {
defaultResolver := &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)), lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
} }
r := &Resolver{ r := &Resolver{
ipv6: config.IPv6, 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)), lruCache: cache.NewLRUCache[string, *D.Msg](cache.WithSize[string, *D.Msg](4096), cache.WithStale[string, *D.Msg](true)),
hosts: config.Hosts, hosts: config.Hosts,
} }
if len(config.Fallback) != 0 { if len(config.Fallback) != 0 {
r.fallback = transform(config.Fallback, defaultResolver, config.PreferH3) r.fallback = transform(config.Fallback, defaultResolver)
} }
if len(config.ProxyServer) != 0 { if len(config.ProxyServer) != 0 {
r.proxyServer = transform(config.ProxyServer, defaultResolver, config.PreferH3) r.proxyServer = transform(config.ProxyServer, defaultResolver)
} }
if len(config.Policy) != 0 { if len(config.Policy) != 0 {
r.policy = trie.New[*Policy]() r.policy = trie.New[*Policy]()
for domain, nameserver := range config.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)))
} }
} }

View File

@ -54,12 +54,12 @@ func isIPRequest(q D.Question) bool {
return q.Qclass == D.ClassINET && (q.Qtype == D.TypeA || q.Qtype == D.TypeAAAA) 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{} ret := []dnsClient{}
for _, s := range servers { for _, s := range servers {
switch s.Net { switch s.Net {
case "https": case "https":
ret = append(ret, newDoHClient(s.Addr, resolver, preferH3, s.ProxyAdapter)) ret = append(ret, newDoHClient(s.Addr, resolver, s.Params, s.ProxyAdapter))
continue continue
case "dhcp": case "dhcp":
ret = append(ret, newDHCPClient(s.Addr)) ret = append(ret, newDHCPClient(s.Addr))

View File

@ -73,7 +73,6 @@ profile:
dns: dns:
enable: false # 关闭将使用系统DNS enable: false # 关闭将使用系统DNS
listen: 0.0.0.0:53 # 开启DNS服务器监听 listen: 0.0.0.0:53 # 开启DNS服务器监听
prefer-h3: false # 启动DOH优先使用http/3
# ipv6: false # false将返回AAAA的空结果 # ipv6: false # false将返回AAAA的空结果
# 用于解析nameserver,fallbacky以及其他DNS服务器配置的,DNS服务域名 # 用于解析nameserver,fallbacky以及其他DNS服务器配置的,DNS服务域名
@ -95,13 +94,15 @@ dns:
# - localhost.ptlogin2.qq.com # - localhost.ptlogin2.qq.com
# DNS主要域名配置 # DNS主要域名配置
# 支持 UDP,TCP,DOT,DOH,DOQ # 支持 UDP,TCP,DoT,DoH,DoQ
# 这部分为主要DNS配置,影响所有直连,确保使用对大陆解析精准的DNS # 这部分为主要DNS配置,影响所有直连,确保使用对大陆解析精准的DNS
nameserver: nameserver:
- 114.114.114.114 # default value - 114.114.114.114 # default value
- 8.8.8.8 # default value - 8.8.8.8 # default value
- tls://223.5.5.5:853 # DNS over TLS - 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 - dhcp://en0 # dns from dhcp
- quic://dns.adguard.com:784 # DNS over QUIC - quic://dns.adguard.com:784 # DNS over QUIC
# - '8.8.8.8#en0' # 兼容指定DNS出口网卡 # - '8.8.8.8#en0' # 兼容指定DNS出口网卡

View File

@ -172,7 +172,6 @@ func updateDNS(c *config.DNS, generalIPv6 bool) {
Default: c.DefaultNameserver, Default: c.DefaultNameserver,
Policy: c.NameServerPolicy, Policy: c.NameServerPolicy,
ProxyServer: c.ProxyServerNameserver, ProxyServer: c.ProxyServerNameserver,
PreferH3: c.PreferH3,
} }
r := dns.NewResolver(cfg) r := dns.NewResolver(cfg)