mirror of
https://github.com/MetaCubeX/mihomo.git
synced 2024-11-16 11:42:43 +08:00
refactor: DoH use fragment setting params
This commit is contained in:
parent
91af078580
commit
21d44fa391
|
@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
149
dns/doh.go
149
dns/doh.go
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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出口网卡
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user