feat: try h3 connect DOH, failed will fall back h2; turn on with dns.prefer-h3: true
This commit is contained in:
parent
8eec86232c
commit
59ab2083aa
7 changed files with 82 additions and 16 deletions
|
@ -79,6 +79,7 @@ type Controller struct {
|
||||||
// DNS config
|
// DNS config
|
||||||
type DNS struct {
|
type DNS struct {
|
||||||
Enable bool `yaml:"enable"`
|
Enable bool `yaml:"enable"`
|
||||||
|
PreferH3 bool `yaml:"prefer-h3"`
|
||||||
IPv6 bool `yaml:"ipv6"`
|
IPv6 bool `yaml:"ipv6"`
|
||||||
NameServer []dns.NameServer `yaml:"nameserver"`
|
NameServer []dns.NameServer `yaml:"nameserver"`
|
||||||
Fallback []dns.NameServer `yaml:"fallback"`
|
Fallback []dns.NameServer `yaml:"fallback"`
|
||||||
|
|
77
dns/doh.go
77
dns/doh.go
|
@ -3,14 +3,17 @@ package dns
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"github.com/Dreamacro/clash/component/dialer"
|
||||||
|
"github.com/Dreamacro/clash/component/resolver"
|
||||||
|
"github.com/lucas-clemente/quic-go"
|
||||||
|
"github.com/lucas-clemente/quic-go/http3"
|
||||||
|
D "github.com/miekg/dns"
|
||||||
|
"go.uber.org/atomic"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"github.com/Dreamacro/clash/component/dialer"
|
|
||||||
"github.com/Dreamacro/clash/component/resolver"
|
|
||||||
|
|
||||||
D "github.com/miekg/dns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,6 +25,9 @@ type dohClient struct {
|
||||||
url string
|
url string
|
||||||
proxyAdapter string
|
proxyAdapter string
|
||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
|
h3Transport *http3.RoundTripper
|
||||||
|
supportH3 *atomic.Bool
|
||||||
|
firstTest *atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
|
||||||
|
@ -63,12 +69,32 @@ func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *dohClient) doRequest(req *http.Request) (*D.Msg, error) {
|
func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
|
||||||
client := &http.Client{Transport: dc.transport}
|
if dc.supportH3.Load() {
|
||||||
|
msg, err = dc.doRequestWithTransport(req, dc.h3Transport)
|
||||||
|
if err != nil {
|
||||||
|
if dc.firstTest.CAS(true, false) {
|
||||||
|
dc.supportH3.Store(false)
|
||||||
|
_ = dc.h3Transport.Close()
|
||||||
|
dc.h3Transport = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msg, err = dc.doRequestWithTransport(req, dc.transport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dc *dohClient) doRequestWithTransport(req *http.Request, transport http.RoundTripper) (*D.Msg, error) {
|
||||||
|
client := &http.Client{Transport: transport}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
buf, err := io.ReadAll(resp.Body)
|
buf, err := io.ReadAll(resp.Body)
|
||||||
|
@ -80,7 +106,7 @@ func (dc *dohClient) doRequest(req *http.Request) (*D.Msg, error) {
|
||||||
return msg, err
|
return msg, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
func newDoHClient(url string, r *Resolver, preferH3 bool, proxyAdapter string) *dohClient {
|
||||||
return &dohClient{
|
return &dohClient{
|
||||||
url: url,
|
url: url,
|
||||||
proxyAdapter: proxyAdapter,
|
proxyAdapter: proxyAdapter,
|
||||||
|
@ -104,5 +130,40 @@ func newDoHClient(url string, r *Resolver, proxyAdapter string) *dohClient {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
h3Transport: &http3.RoundTripper{
|
||||||
|
Dial: func(ctx context.Context, network, 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
supportH3: atomic.NewBool(preferH3),
|
||||||
|
firstTest: atomic.NewBool(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -365,6 +365,7 @@ 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
|
||||||
|
@ -378,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),
|
main: transform(config.Default, nil, config.PreferH3),
|
||||||
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),
|
main: transform(config.Main, defaultResolver, config.PreferH3),
|
||||||
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)
|
r.fallback = transform(config.Fallback, defaultResolver, config.PreferH3)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(config.ProxyServer) != 0 {
|
if len(config.ProxyServer) != 0 {
|
||||||
r.proxyServer = transform(config.ProxyServer, defaultResolver)
|
r.proxyServer = transform(config.ProxyServer, defaultResolver, config.PreferH3)
|
||||||
}
|
}
|
||||||
|
|
||||||
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)))
|
_ = r.policy.Insert(domain, NewPolicy(transform([]NameServer{nameserver}, defaultResolver, config.PreferH3)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) []dnsClient {
|
func transform(servers []NameServer, resolver *Resolver, preferH3 bool) []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, s.ProxyAdapter))
|
ret = append(ret, newDoHClient(s.Addr, resolver, preferH3, s.ProxyAdapter))
|
||||||
continue
|
continue
|
||||||
case "dhcp":
|
case "dhcp":
|
||||||
ret = append(ret, newDHCPClient(s.Addr))
|
ret = append(ret, newDHCPClient(s.Addr))
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -58,6 +58,7 @@ require (
|
||||||
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
github.com/klauspost/cpuid/v2 v2.0.12 // indirect
|
||||||
github.com/kr/pretty v0.2.1 // indirect
|
github.com/kr/pretty v0.2.1 // indirect
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 // indirect
|
||||||
|
github.com/marten-seemann/qpack v0.2.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
|
||||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||||
|
|
1
go.sum
1
go.sum
|
@ -223,6 +223,7 @@ github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40 h1:EnfXoSqDfSNJv0
|
||||||
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
github.com/lunixbochs/struc v0.0.0-20200707160740-784aaebc1d40/go.mod h1:vy1vK6wD6j7xX6O6hXe621WabdtNkou2h7uRtTfRMyg=
|
||||||
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
|
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
|
||||||
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
|
||||||
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
|
||||||
|
|
|
@ -163,6 +163,7 @@ 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 a new issue