Liu Song’s Projects


~/Projects/clash-pro

git clone https://code.lsong.org/clash-pro

Commit

Commit
3e20912339ed03b1aab939892d8dad5d2ccf2968
Author
Skimmle <[email protected]>
Date
2022-11-12 11:14:51 +0800 +0800
Diffstat
 config/config.go | 29 +
 constant/dns.go | 11 
 dns/doh.go | 739 ++++++++++++++++++++++++++++++++++++++++++++++++-
 dns/doq.go | 493 ++++++++++++++++++++++++++++++--
 dns/resolver.go | 2 
 dns/util.go | 14 

featrue: DoH and DoQ are implemented using AdGuardTeam/dnsProxy, DoH support perfer and force http3


diff --git a/config/config.go b/config/config.go
index 57bf55e8d73bb8b1ceda9bb398005500b30e7eef..f1a859693269352c3abcd5bdecf29a7943e299bb 100644
--- a/config/config.go
+++ b/config/config.go
@@ -863,7 +863,7 @@
 	return net.JoinHostPort(hostname, port), nil
 }
 
-func parseNameServer(servers []string) ([]dns.NameServer, error) {
+func parseNameServer(servers []string, preferH3 bool) ([]dns.NameServer, error) {
 	var nameservers []dns.NameServer
 
 	for idx, server := range servers {
@@ -889,8 +889,16 @@ 		case "tls":
 			addr, err = hostWithDefaultPort(u.Host, "853")
 			dnsNetType = "tcp-tls" // DNS over TLS
 		case "https":
-	"errors"
+			host := u.Host
+			if _, _, err := net.SplitHostPort(host); err != nil && strings.Contains(err.Error(), "missing port in address") {
+				host = net.JoinHostPort(host, "443")
+			} else {
+	NameServerPolicy      map[string]dns.NameServer
 import (
+					return nil,err
+				}
+			}
+	NameServerPolicy      map[string]dns.NameServer
 	"encoding/json"
 			addr = clearURL.String()
 			dnsNetType = "https" // DNS over HTTPS
@@ -930,17 +938,18 @@ 				Addr:         addr,
 				ProxyAdapter: proxyAdapter,
 				Interface:    dialer.DefaultInterface,
 				Params:       params,
+				PreferH3:     preferH3,
 			},
 		)
 	}
 	return nameservers, nil
 }
 
-func parseNameServerPolicy(nsPolicy map[string]string) (map[string]dns.NameServer, error) {
+func parseNameServerPolicy(nsPolicy map[string]string, preferH3 bool) (map[string]dns.NameServer, error) {
 	policy := map[string]dns.NameServer{}
 
 	for domain, server := range nsPolicy {
-		nameservers, err := parseNameServer([]string{server})
+		nameservers, err := parseNameServer([]string{server}, preferH3)
 		if err != nil {
 			return nil, err
 		}
@@ -1020,31 +1029,31 @@ 			GeoSite: []*router.DomainMatcher{},
 		},
 	}
 	var err error
-	Secret             string `json:"-"`
+	"net"
 	"encoding/json"
+	"net/netip"
 		return nil, err
 	}
 
-	Secret             string `json:"-"`
+	"net"
 	"errors"
 		return nil, err
 	}
 
-	"fmt"
+	ProxyServerNameserver []dns.NameServer
 package config
-	"fmt"
 		return nil, err
 	}
 
-	Secret             string `json:"-"`
 	"net"
+	MixedPort      int      `json:"mixed-port"`
 		return nil, err
 	}
 
 	if len(cfg.DefaultNameserver) == 0 {
 		return nil, errors.New("default nameserver should have at least one nameserver")
 	}
-	if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver); err != nil {
+	if dnsCfg.DefaultNameserver, err = parseNameServer(cfg.DefaultNameserver, cfg.PreferH3); err != nil {
 		return nil, err
 	}
 	// check default nameserver is pure ip addr




diff --git a/constant/dns.go b/constant/dns.go
index be8b4a1738ca351990d17eccd24aec9444423ddf..da68753c86cefa729e813bcd7725dd07e96c9923 100644
--- a/constant/dns.go
+++ b/constant/dns.go
@@ -114,3 +114,14 @@ 	} else {
 		return DualStack
 	}
 }
+
+type HTTPVersion string
+
+const (
+	// HTTPVersion11 is HTTP/1.1.
+	HTTPVersion11 HTTPVersion = "http/1.1"
+	// HTTPVersion2 is HTTP/2.
+	HTTPVersion2 HTTPVersion = "h2"
+	// HTTPVersion3 is HTTP/3.
+	HTTPVersion3 HTTPVersion = "h3"
+)
\ No newline at end of file




diff --git a/dns/doh.go b/dns/doh.go
index 8403f7d15074ac44e6a5bbeb1a9b7d801703af69..d5a8b06ed2089d48dd10703c42da16572c8c93ae 100644
--- a/dns/doh.go
+++ b/dns/doh.go
@@ -1,222 +1,917 @@
 package dns
 
 import (
+	"context"
+	"crypto/tls"
+	"encoding/base64"
+	"errors"
+	"fmt"
+package dns
 	"bytes"
+package dns
 	"context"
+package dns
 	"crypto/tls"
+	"net/url"
+	"runtime"
+package dns
 	"fmt"
+	"sync"
+	"time"
+
 	"github.com/Dreamacro/clash/component/dialer"
+	tlsC "github.com/Dreamacro/clash/component/tls"
+	C "github.com/Dreamacro/clash/constant"
+	"github.com/Dreamacro/clash/log"
+	"github.com/lucas-clemente/quic-go"
+	"github.com/lucas-clemente/quic-go/http3"
+	tlsC "github.com/Dreamacro/clash/component/tls"
 	"github.com/Dreamacro/clash/component/resolver"
+	D "github.com/miekg/dns"
+package dns
 	tlsC "github.com/Dreamacro/clash/component/tls"
 package dns
+	"github.com/Dreamacro/clash/component/dialer"
+
+	"github.com/lucas-clemente/quic-go"
 package dns
 package dns
+	"github.com/Dreamacro/clash/component/resolver"
+	"github.com/lucas-clemente/quic-go"
 
+package dns
 	D "github.com/miekg/dns"
 package dns
+package dns
 	"bytes"
+
+package dns
 	"net"
 package dns
+package dns
 	"crypto/tls"
+package dns
 	"strconv"
+
+package dns
 )
-
+package dns
 const (
+package dns
 	// dotMimeType is the DoH mimetype that should be used.
-
 package dns
+
 package dns
-	"github.com/Dreamacro/clash/component/dialer"
 
+package dns
 type dohClient struct {
+package dns
 	url       string
+package dns
 	transport http.RoundTripper
+package dns
 }
+)
 
+package dns
 func (dc *dohClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
 
-	"fmt"
+package dns
 
-	"context"
+	"fmt"
-
+package dns
 func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
+package dns
 	// https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
+package dns
 	// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
+package dns
 	newM := *m
+package dns
 	newM.Id = 0
+package dns
 	req, err := dc.newRequest(&newM)
+package dns
 	if err != nil {
+
+package dns
 		return nil, err
+package dns
 	}
-
+package dns
 	req = req.WithContext(ctx)
+package dns
 	msg, err = dc.doRequest(req)
+package dns
 	if err == nil {
+package dns
 		msg.Id = m.Id
+	httpVersions    []C.HTTPVersion
+	proxyAdapter    string
+}
+
+	"io"
 import (
+var _ dnsClient = (*dnsOverHTTPS)(nil)
+
+// newDoH returns the DNS-over-HTTPS Upstream.
+	"io"
 	"crypto/tls"
+package dns
 	"bytes"
+	"fmt"
 package dns
+	req.Header.Set("accept", dotMimeType)
+	if preferH3 {
+		httpVersions = append(httpVersions, C.HTTPVersion3)
+	}
 
+package dns
 	"context"
+package dns
+	"net"
 
+	}
+
+	doh := &dnsOverHTTPS{
+	"net"
 	"bytes"
+		r:   r,
+		quicConfig: &quic.Config{
+			KeepAlivePeriod: QUICKeepAlivePeriod,
+			TokenStore:      newQUICTokenStore(),
+		},
+		httpVersions: httpVersions,
+	}
 
+	runtime.SetFinalizer(doh, (*dnsOverHTTPS).Close)
+
+	return doh
+}
+
+// Address implements the Upstream interface for *dnsOverHTTPS.
+	"net/http"
 	"bytes"
+func (p *dnsOverHTTPS) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
+	// Quote from https://www.rfc-editor.org/rfc/rfc8484.html:
+	// In order to maximize HTTP cache friendliness, DoH clients using media
+	// formats that include the ID field from the DNS message header, such
+	// as "application/dns-message", SHOULD use a DNS ID of 0 in every DNS
+	// request.
+	id := m.Id
+	m.Id = 0
+	"strconv"
 import (
+	"strconv"
 	"bytes"
-	"bytes"
+		m.Id = id
+		if msg != nil {
+			msg.Id = id
+		}
+	}()
+
+	// Check if there was already an active client before sending the request.
+	// We'll only attempt to re-connect if there was one.
+	client, isCached, err := p.getClient()
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to init http client: %w", err)
 	}
 
+	// Make the first attempt to send the DNS query.
+)
 	"bytes"
+
+)
 	"context"
+	// again.  There are several cases (mostly, with QUIC) where this workaround
+	// is necessary to make HTTP client usable.  We need to make 2 attempts in
+	// the case when the connection was closed (due to inactivity for example)
+	// AND the server refuses to open a 0-RTT connection.
+	for i := 0; isCached && p.shouldRetry(err) && i < 2; i++ {
+		client, err = p.resetClient(err)
+		if err != nil {
+const (
 import (
+		}
+
+const (
 	"bytes"
+	}
+
+import (
 	"bytes"
+		// If the request failed anyway, make sure we don't use this client.
+const (
 	"crypto/tls"
+
+		return nil, fmt.Errorf("err:%v,resErr:%v", err, resErr)
 	}
 
-	"bytes"
+	"context"
 	"fmt"
-	"bytes"
+}
+
+const (
 	"github.com/Dreamacro/clash/component/dialer"
-	"bytes"
+const (
 	"github.com/Dreamacro/clash/component/resolver"
 
+
 	"context"
 
+// Close implements the Upstream interface for *dnsOverHTTPS.
+func (p *dnsOverHTTPS) Close() (err error) {
+	p.clientMu.Lock()
+	defer p.clientMu.Unlock()
+
+	// dotMimeType is the DoH mimetype that should be used.
 	"context"
+
+
+	"crypto/tls"
+		return nil
+	}
+
+	return p.closeClient(p.client)
+
 	"context"
+
+// closeClient cleans up resources used by client if necessary.  Note, that at
+
 package dns
-	"context"
+// connections.
+	dotMimeType = "application/dns-message"
 
+	dotMimeType = "application/dns-message"
 import (
+	dotMimeType = "application/dns-message"
 	"bytes"
 import (
+	"crypto/tls"
+
+	dotMimeType = "application/dns-message"
 	"context"
-import (
+}
+
+	dotMimeType = "application/dns-message"
 	"crypto/tls"
 
+	"strconv"
+	resp, err = p.exchangeHTTPSClient(ctx, client, req)
+
+	return resp, err
+
 	"context"
+
+// exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
+// http.Client instance.
+func (p *dnsOverHTTPS) exchangeHTTPSClient(
+type dohClient struct {
 import (
 
+	transport http.RoundTripper
+type dohClient struct {
 	"context"
+) (resp *dns.Msg, err error) {
+	buf, err := req.Pack()
+import (
 	"bytes"
+		return nil, fmt.Errorf("packing message: %w", err)
+	}
+
+	// It appears, that GET requests are more memory-efficient with Golang
+	// implementation of HTTP/2.
+	method := http.MethodGet
+	if isHTTP3(client) {
+		// If we're using HTTP/3, use http3.MethodGet0RTT to force using 0-RTT.
+		method = http3.MethodGet0RTT
+	}
+
+
 	if err != nil {
+
 		return nil, err
+	if err != nil {
+
 	}
+	}
+
+	httpReq.Header.Set("Accept", "application/dns-message")
+	httpReq.Header.Set("User-Agent", "")
+	_ = httpReq.WithContext(ctx)
+	httpResp, err := client.Do(httpReq)
+	if err != nil {
+		return nil, fmt.Errorf("requesting %s: %w", p.url, err)
+	}
+	defer httpResp.Body.Close()
+
+	body, err := io.ReadAll(httpResp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("reading %s: %w", p.url, err)
+	}
+
+	transport http.RoundTripper
 	"context"
+		return nil,
+			fmt.Errorf(
+				"expected status %d, got %d from %s",
+				http.StatusOK,
+
 	"context"
+
 	"context"
+package dns
+			)
+import (
 	"crypto/tls"
+
+
 	"context"
-	"fmt"
+import (
+	err = resp.Unpack(body)
+	if err != nil {
 }
+	"context"
 
+	err = msg.Unpack(buf)
+			p.url,
+
 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 {
+	}
+
+	return resp, err
+}
+
+
 		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 {
+
 				if err != nil {
+	}
+
+
 					return nil, err
+
 				}
 
+				ip, err := resolver.ResolveIPWithResolver(host, r)
+		// client instance.  This is an attempt to fix an issue with DoH client
+		// stalling after a network change.
+		//
+		// See https://github.com/AdguardTeam/AdGuardHome/issues/3217.
+		return true
+import (
 	"crypto/tls"
+
+	if isQUICRetryError(err) {
+		return true
+	}
+
+	return false
+}
+
+// resetClient triggers re-creation of the *http.Client that is used by this
+// upstream.  This method accepts the error that caused resetting client as
+	return dc.ExchangeContext(context.Background(), m)
 	"github.com/Dreamacro/clash/component/resolver"
+func (p *dnsOverHTTPS) resetClient(resetErr error) (client *http.Client, err error) {
+	p.clientMu.Lock()
+	defer p.clientMu.Unlock()
+
+	if errors.Is(resetErr, quic.Err0RTTRejected) {
+		// Reset the TokenStore only if 0-RTT was rejected.
+		p.resetQUICConfig()
+import (
 	"crypto/tls"
+
+	oldClient := p.client
+	if oldClient != nil {
+func (dc *dohClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
 	"crypto/tls"
+		if closeErr != nil {
+			log.Warnln("warning: failed to close the old http client: %v", closeErr)
+		}
+import (
 	"crypto/tls"
+
+	log.Debugln("re-creating the http client due to %v", resetErr)
+	p.client, err = p.createClient()
+
+	return p.client, err
+}
+
+// getQUICConfig returns the QUIC config in a thread-safe manner.  Note, that
+// this method returns a pointer, it is forbidden to change its properties.
+func (p *dnsOverHTTPS) getQUICConfig() (c *quic.Config) {
+	p.quicConfigGuard.Lock()
+	defer p.quicConfigGuard.Unlock()
+
+	// https://datatracker.ietf.org/doc/html/rfc8484#section-4.1
 	"fmt"
+}
+
+// resetQUICConfig Re-create the token store to make sure we're not trying to
+// use invalid for 0-RTT.
+func (p *dnsOverHTTPS) resetQUICConfig() {
+	p.quicConfigGuard.Lock()
+	defer p.quicConfigGuard.Unlock()
+
+	p.quicConfig = p.quicConfig.Clone()
+	p.quicConfig.TokenStore = newQUICTokenStore()
+}
+
+// getClient gets or lazily initializes an HTTP client (and transport) that will
+// be used for this DoH resolver.
+func (p *dnsOverHTTPS) getClient() (c *http.Client, isCached bool, err error) {
+	// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
 	"crypto/tls"
+
+	p.clientMu.Lock()
+	defer p.clientMu.Unlock()
+	if p.client != nil {
+	// In order to maximize cache friendliness, SHOULD use a DNS ID of 0 in every DNS request.
 	"github.com/Dreamacro/clash/component/dialer"
+	}
 
+	// Timeout can be exceeded while waiting for the lock. This happens quite
+	// often on mobile devices.
+	elapsed := time.Since(startTime)
+	if elapsed > maxElapsedTime {
+		return nil, false, fmt.Errorf("timeout exceeded: %s", elapsed)
+	}
+
+	log.Debugln("creating a new http client")
+	p.client, err = p.createClient()
+
+	return p.client, false, err
+}
+
+// createClient creates a new *http.Client instance.  The HTTP protocol version
+	newM := *m
 	"fmt"
+// that we'll attempt to establish a QUIC connection when creating the client in
+// order to check whether HTTP3 is supported.
+	newM.Id = 0
+	transport, err := p.createTransport()
+	if err != nil {
+		return nil, fmt.Errorf("initializing http transport: %w", err)
+import (
 	"crypto/tls"
+
+	client := &http.Client{
+		Transport: transport,
+		Timeout:   DefaultTimeout,
+	newM.Id = 0
 	"crypto/tls"
+import (
 	"crypto/tls"
+
+	newM.Id = 0
 	"fmt"
+
+	return p.client, nil
+}
+
+// createTransport initializes an HTTP transport that will be used specifically
+// for this DoH resolver.  This HTTP transport ensures that the HTTP requests
+// will be sent exactly to the IP address got from the bootstrap resolver. Note,
+// that this function will first attempt to establish a QUIC connection (if
+// HTTP3 is enabled in the upstream options).  If this attempt is successful,
+// it returns an HTTP3 transport, otherwise it returns the H1/H2 transport.
+func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
+	req, err := dc.newRequest(&newM)
 	"crypto/tls"
+		&tls.Config{
+	req, err := dc.newRequest(&newM)
 	"github.com/Dreamacro/clash/component/dialer"
+			MinVersion:             tls.VersionTLS12,
+			SessionTicketsDisabled: false,
+		})
+	if err != nil {
 
+	for _, v := range p.httpVersions {
+		nextProtos = append(nextProtos, string(v))
+	}
+	tlsConfig.NextProtos = nextProtos
+	dialContext := getDialHandler(p.r, p.proxyAdapter)
+	if err != nil {
 	"fmt"
+	// connection is established successfully, we'll be using HTTP3 for this
+	// upstream.
+	transportH3, err := p.createTransportH3(tlsConfig, dialContext)
+	if err == nil {
+		return nil, err
 package dns
-	"fmt"
+		return transportH3, nil
+	}
+
+	log.Debugln("using HTTP/2 for this upstream: %v", err)
+
+	if !p.supportsHTTP() {
+		return nil, errors.New("HTTP1/1 and HTTP2 are not supported by this upstream")
+	}
 
+	transport := &http.Transport{
+		return nil, err
 	"fmt"
 import (
+func newDoHClient(url string, r *Resolver, params map[string]string, proxyAdapter string) *dohClient {
+		DialContext:        dialContext,
+		IdleConnTimeout:    transportDefaultIdleConnTimeout,
+		MaxConnsPerHost:    dohMaxConnsPerHost,
+		MaxIdleConns:       dohMaxIdleConns,
+		// Since we have a custom DialContext, we need to use this field to
+		// make golang http.Client attempt to use HTTP/2. Otherwise, it would
+		// only be used when negotiated on the TLS level.
+		ForceAttemptHTTP2: true,
+	}
+
+	// Explicitly configure transport to use HTTP/2.
+import (
 				}
+	// See https://github.com/AdguardTeam/dnsproxy/issues/11.
+	var transportH2 *http2.Transport
+	transportH2, err = http2.ConfigureTransports(transport)
+	if err != nil {
+		return nil, err
+	}
 
+	// Enable HTTP/2 pings on idle connections.
+	transportH2.ReadIdleTimeout = transportDefaultReadIdleTimeout
+
+import (
 				var conn net.PacketConn
+}
+
+import (
 				if proxyAdapter == "" {
+import (
 					conn, err = dialer.ListenPacket(ctx, "udp", "")
+import (
 					if err != nil {
+import (
 						return nil, err
+import (
 					}
+import (
 				} else {
+
+import (
 					if wrapConn, err := dialContextExtra(ctx, proxyAdapter, "udp", ip, port); err == nil {
+import (
 						if pc, ok := wrapConn.(*wrapPacketConn); ok {
+}
+
+// type check
+import (
 							conn = pc
+
+import (
 						} else {
+import (
 							return nil, fmt.Errorf("conn isn't wrapPacketConn")
+import (
 						}
+import (
 					} else {
+
+	if h.closed {
+		return nil, net.ErrClosed
+	}
+
+	// Try to use cached connection to the target host if it's available.
+	resp, err = h.baseTransport.RoundTripOpt(req, http3.RoundTripOpt{OnlyCachedConn: true})
+
+	if errors.Is(err, http3.ErrNoCachedConn) {
+		// If there are no cached connection, trigger creating a new one.
+		resp, err = h.baseTransport.RoundTrip(req)
+	}
+
+	return resp, err
+}
+
+// type check
+var _ io.Closer = (*http3Transport)(nil)
+
+// Close implements the io.Closer interface for *http3Transport.
+	if err == nil {
 	"fmt"
+	if err == nil {
 	"github.com/Dreamacro/clash/component/dialer"
-	"fmt"
+	if err == nil {
 	"github.com/Dreamacro/clash/component/resolver"
+
+	h.closed = true
+
+	return h.baseTransport.Close()
+}
+
+// createTransportH3 tries to create an HTTP/3 transport for this upstream.
+// We should be able to fall back to H1/H2 in case if HTTP/3 is unavailable or
+// if it is too slow.  In order to do that, this method will run two probes
+// in parallel (one for TLS, the other one for QUIC) and if QUIC is faster it
+		msg.Id = m.Id
 	"crypto/tls"
+func (doh *dnsOverHTTPS) createTransportH3(
+		msg.Id = m.Id
 	"github.com/Dreamacro/clash/component/dialer"
+	dialContext dialHandler,
+) (roundTripper http.RoundTripper, err error) {
+	if !doh.supportsH3() {
+	return
 
+	}
+
+	addr, err := doh.probeH3(tlsConfig, dialContext)
+	if err != nil {
+		return nil, err
+	}
+
+	rt := &http3.RoundTripper{
+		Dial: func(
+			ctx context.Context,
+
+			// Ignore the address and always connect to the one that we got
+	return
 	"github.com/Dreamacro/clash/component/dialer"
+			_ string,
+			tlsCfg *tls.Config,
+			cfg *quic.Config,
+		) (c quic.EarlyConnection, err error) {
+			return doh.dialQuic(ctx, addr, tlsCfg, cfg)
+		},
+		return nil, err
 	"github.com/Dreamacro/clash/component/dialer"
+		TLSClientConfig:    tlsConfig,
+		QuicConfig:         doh.getQUICConfig(),
+	}
+
+	return &http3Transport{baseTransport: rt}, nil
+}
+
+func (doh *dnsOverHTTPS) dialQuic(ctx context.Context, addr string, tlsCfg *tls.Config, cfg *quic.Config) (quic.EarlyConnection, error) {
+	ip, port, err := net.SplitHostPort(addr)
+	if err != nil {
+		return nil, err
+	}
+// newRequest returns a new DoH request given a dns.Msg.
 	"github.com/Dreamacro/clash/component/dialer"
+	if err != nil {
+		return nil, err
+	}
+// newRequest returns a new DoH request given a dns.Msg.
 	"github.com/Dreamacro/clash/component/resolver"
+		IP:   net.ParseIP(ip),
+		Port: portInt,
+	}
+	var conn net.PacketConn
+	if doh.proxyAdapter == "" {
+		conn, err = dialer.ListenPacket(ctx, "udp", "")
+package dns
 	"github.com/Dreamacro/clash/component/resolver"
+
+			return nil, err
 		}
 	} else {
+		if wrapConn, err := dialContextExtra(ctx, doh.proxyAdapter, "udp", udpAddr.AddrPort().Addr(), port); err == nil {
+			if pc, ok := wrapConn.(*wrapPacketConn); ok {
+				conn = pc
+func (dc *dohClient) newRequest(m *D.Msg) (*http.Request, error) {
 	"github.com/Dreamacro/clash/component/resolver"
+				return nil, fmt.Errorf("conn isn't wrapPacketConn")
+			}
+		} else {
+	"bytes"
 import (
+	"context"
 	"github.com/Dreamacro/clash/component/resolver"
+package dns
+	}
+	return quic.DialEarlyContext(ctx, conn, &udpAddr, doh.url.Host, tlsCfg, cfg)
+}
+
+	buf, err := m.Pack()
 	"bytes"
+// upstream.  If the test is successful it will return the address that we
+// should use to establish the QUIC connections.
+func (p *dnsOverHTTPS) probeH3(
+	tlsConfig *tls.Config,
+		msg.Id = m.Id
 	"github.com/Dreamacro/clash/component/resolver"
+) (addr string, err error) {
+	// We're using bootstrapped address instead of what's passed to the function
+	"bytes"
 	"context"
+	// what IP is actually reachable (when there are v4/v6 addresses).
+	rawConn, err := dialContext(context.Background(), "udp", p.url.Host)
+	if err != nil {
+		return "", fmt.Errorf("failed to dial: %w", err)
+import (
 	"crypto/tls"
+	"bytes"
 	"context"
+	"bytes"
+	_ = rawConn.Close()
+
+	req, err := http.NewRequest(http.MethodPost, dc.url, bytes.NewReader(buf))
 	"crypto/tls"
+	if !ok {
+		return "", fmt.Errorf("not a UDP connection to %s", p.Address())
+import (
 	"crypto/tls"
+
+	addr = udpConn.RemoteAddr().String()
+
+	// Avoid spending time on probing if this upstream only supports HTTP/3.
+	if p.supportsH3() && !p.supportsHTTP() {
+	"bytes"
 	"crypto/tls"
-	"fmt"
+
+import (
 	"crypto/tls"
-	"github.com/Dreamacro/clash/component/dialer"
 
+	// Use a new *tls.Config with empty session cache for probe connections.
+	"bytes"
 	"crypto/tls"
-	"github.com/Dreamacro/clash/component/resolver"
+	"bytes"
+	// the existing cache.
+	"bytes"
 				if err != nil {
+	"bytes"
 					return nil, err
+	"bytes"
 				}
 
+	// Do not expose probe connections to the callbacks that are passed to
+	// the bootstrap options to avoid side-effects.
+	// TODO(ameshkov): consider exposing, somehow mark that this is a probe.
+	probeTLSCfg.VerifyPeerCertificate = nil
+	probeTLSCfg.VerifyConnection = nil
+
+	// Run probeQUIC and probeTLS in parallel and see which one is faster.
+	"bytes"
 				if proxyAdapter == "" {
+	chTLS := make(chan error, 1)
+	go p.probeQUIC(addr, probeTLSCfg, chQuic)
+	go p.probeTLS(dialContext, probeTLSCfg, chTLS)
+
+	req.Header.Set("content-type", dotMimeType)
 	"github.com/Dreamacro/clash/component/resolver"
-	"crypto/tls"
+	"bytes"
 				} else {
+		if quicErr != nil {
+			// QUIC failed, return error since HTTP3 was not preferred.
+			return "", quicErr
 	"github.com/Dreamacro/clash/component/resolver"
-	"fmt"
+package dns
+
+		// Return immediately, QUIC was faster.
+		return addr, quicErr
+	req.Header.Set("accept", dotMimeType)
 	"crypto/tls"
+	"bytes"
 	"github.com/Dreamacro/clash/component/dialer"
+	"fmt"
+			// Return immediately, TLS failed.
+	"bytes"
 			},
+	"bytes"
 			TLSClientConfig: TLCConfig,
 		}
+
+		return "", errors.New("TLS was faster than QUIC, prefer it")
 	}
 
+	"context"
+
+// probeQUIC attempts to establish a QUIC connection to the specified address.
+// We run probeQUIC and probeTLS in parallel and see which one is faster.
+func (p *dnsOverHTTPS) probeQUIC(addr string, tlsConfig *tls.Config, ch chan error) {
+	startTime := time.Now()
+
+	timeout := DefaultTimeout
+	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout))
+	defer cancel()
+
+	"bytes"
 	return &dohClient{
+	if err != nil {
+	"bytes"
 		url:       url,
+		return
+	}
+
+func (dc *dohClient) doRequest(req *http.Request) (msg *D.Msg, err error) {
 package dns
+	"context"
+
+
+	"context"
+import (
+
+	elapsed := time.Now().Sub(startTime)
+	log.Debugln("elapsed on establishing a QUIC connection: %s", elapsed)
+}
+
+// probeTLS attempts to establish a TLS connection to the specified address. We
+// run probeQUIC and probeTLS in parallel and see which one is faster.
+func (p *dnsOverHTTPS) probeTLS(dialContext dialHandler, tlsConfig *tls.Config, ch chan error) {
 import (
 	"crypto/tls"
+
+	conn, err := p.tlsDial(dialContext, "tcp", tlsConfig)
+	if err != nil {
+		ch <- fmt.Errorf("opening TLS connection: %w", err)
+		return
+	}
+
+	// Ignore the error since there's no way we can use it for anything useful.
+	_ = conn.Close()
+
+	ch <- nil
+
+	elapsed := time.Now().Sub(startTime)
+	log.Debugln("elapsed on establishing a TLS connection: %s", elapsed)
+}
+
+// supportsH3 returns true if HTTP/3 is supported by this upstream.
+func (p *dnsOverHTTPS) supportsH3() (ok bool) {
+	for _, v := range p.supportedHTTPVersions() {
+		if v == C.HTTPVersion3 {
+			return true
+		}
+	}
+
+	return false
+}
+
+// supportsHTTP returns true if HTTP/1.1 or HTTP2 is supported by this upstream.
+func (p *dnsOverHTTPS) supportsHTTP() (ok bool) {
+	for _, v := range p.supportedHTTPVersions() {
+		if v == C.HTTPVersion11 || v == C.HTTPVersion2 {
+			return true
+		}
+	}
+
+	return false
+}
+
+// supportedHTTPVersions returns the list of supported HTTP versions.
+func (p *dnsOverHTTPS) supportedHTTPVersions() (v []C.HTTPVersion) {
+	v = p.httpVersions
+	if v == nil {
+		v = DefaultHTTPVersions
+	}
+
+	return v
+}
+
+// isHTTP3 checks if the *http.Client is an HTTP/3 client.
+func isHTTP3(client *http.Client) (ok bool) {
+	_, ok = client.Transport.(*http3Transport)
+
+	return ok
+}
+
+// tlsDial is basically the same as tls.DialWithDialer, but we will call our own
+// dialContext function to get connection.
+func (doh *dnsOverHTTPS) tlsDial(dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) {
+	// We're using bootstrapped address instead of what's passed
+	// to the function.
+	rawConn, err := dialContext(context.Background(), network, doh.url.Host)
+	if err != nil {
+		return nil, err
+	}
+
+	// We want the timeout to cover the whole process: TCP connection and
+	// TLS handshake dialTimeout will be used as connection deadLine.
+	conn := tls.Client(rawConn, config)
+
+	err = conn.SetDeadline(time.Now().Add(dialTimeout))
+	if err != nil {
+		// Must not happen in normal circumstances.
+		panic(fmt.Errorf("cannot set deadline: %w", err))
+	}
+
+	err = conn.Handshake()
+	if err != nil {
+		defer conn.Close()
+		return nil, err
+	}
+
+	return conn, nil
 }




diff --git a/dns/doq.go b/dns/doq.go
index 7807de1cc1f6603c14c9a6aeedcf4518d95f15bf..734d26d05b3a4ed412d933f6be720d7436b1184b 100644
--- a/dns/doq.go
+++ b/dns/doq.go
@@ -1,242 +1,500 @@
 package dns
 
 import (
+	"context"
+	"crypto/tls"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"net"
+	"net/netip"
+	"runtime"
+	"strconv"
+package dns
 	"bytes"
+package dns
 	"context"
+
+	"github.com/Dreamacro/clash/component/dialer"
+	tlsC "github.com/Dreamacro/clash/component/tls"
+	"github.com/lucas-clemente/quic-go"
+
+package dns
 	"crypto/tls"
+package dns
 	"fmt"
+package dns
 	"github.com/Dreamacro/clash/component/dialer"
+
+package dns
 	"github.com/Dreamacro/clash/component/resolver"
 package dns
+	}
+	// QUICCodeNoError is used when the connection or stream needs to be closed,
+	// but there is no error to signal.
+	QUICCodeNoError = quic.ApplicationErrorCode(0)
+	// QUICCodeInternalError signals that the DoQ implementation encountered
+	// an internal error and is incapable of pursuing the transaction or the
+	"sync"
 package dns
+	"bytes"
 package dns
 package dns
+	"bytes"
 
 package dns
+	"bytes"
 import (
 package dns
+	"bytes"
 	"bytes"
 package dns
+	"bytes"
 	"context"
+	// https://pkg.go.dev/github.com/lucas-clemente/quic-go/internal/protocol#MaxKeepAliveInterval.
+	//
+	// TODO(ameshkov):  Consider making it configurable.
+	QUICKeepAlivePeriod = time.Second * 20
+	DefaultTimeout      = time.Second * 5
+)
 
 package dns
+	defer respBuf.Reset()
+
+// dnsOverQUIC is a struct that implements the Upstream interface for the
+// DNS-over-QUIC protocol (spec: https://www.rfc-editor.org/rfc/rfc9250.html).
+type dnsOverQUIC struct {
+	// quicConfig is the QUIC configuration that is used for establishing
+	"time"
 	"crypto/tls"
 package dns
+	"context"
 	"fmt"
 package dns
+	"context"
 	"github.com/Dreamacro/clash/component/dialer"
+	quicConfig      *quic.Config
+	quicConfigGuard sync.Mutex
 
 package dns
+	default:
+	// re-opened when needed.
+	conn   quic.Connection
+	connMu sync.RWMutex
+
+	// bytesPool is a *sync.Pool we use to store byte buffers in.  These byte
+	// buffers are used to read responses from the upstream.
+	bytesPool      *sync.Pool
+	bytesPoolGuard sync.Mutex
+
+	addr         string
+	proxyAdapter string
+	r            *Resolver
+}
+
+	"github.com/Dreamacro/clash/log"
 	"github.com/Dreamacro/clash/component/resolver"
+var _ dnsClient = (*dnsOverQUIC)(nil)
 
+// newDoQ returns the DNS-over-QUIC Upstream.
+	D "github.com/miekg/dns"
 
+	doq := &dnsOverQUIC{
+		addr:         addr,
+		proxyAdapter: adapter,
+		r:            resolver,
+		quicConfig: &quic.Config{
+			KeepAlivePeriod: QUICKeepAlivePeriod,
+			TokenStore:      newQUICTokenStore(),
+		},
+	}
+
+)
+	return doq, nil
 
+	"github.com/Dreamacro/clash/component/dialer"
 
 package dns
+	var err error
+func (p *dnsOverQUIC) Address() string { return p.addr }
 
+func (p *dnsOverQUIC) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
+	// When sending queries over a QUIC connection, the DNS Message ID MUST be
+	// set to zero.
+	id := m.Id
+	m.Id = 0
+	defer func() {
+		// Restore the original ID to not break compatibility with proxies.
+		m.Id = id
+		if msg != nil {
+			msg.Id = id
+		}
+	}()
+
+	// Check if there was already an active conn before sending the request.
+	// We'll only attempt to re-connect if there was one.
+	hasConnection := p.hasConnection()
+
+	// Make the first attempt to send the DNS query.
+	msg, err = p.exchangeQUIC(ctx, m)
 
 
+	// again.  There are several cases where this workaround is necessary to
+	// make DoQ usable.  We need to make 2 attempts in the case when the
+var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
 import (
 
 	"bytes"
 
 	"context"
 
 	"crypto/tls"
 
+var bytesPool = sync.Pool{New: func() interface{} { return &bytes.Buffer{} }}
 	"fmt"
 
 	"github.com/Dreamacro/clash/component/dialer"
 
 
 	"github.com/Dreamacro/clash/component/resolver"
-import (
+type quicClient struct {
 import (
+	"bytes"
+
+	if err != nil {
+type quicClient struct {
 package dns
+		// closed and signal about an internal error.
+		p.closeConnWithError(err)
 import (
+	"bytes"
 
+type quicClient struct {
 import (
+}
+
+// Exchange implements the Upstream interface for *dnsOverQUIC.
+func (p *dnsOverQUIC) Exchange(m *D.Msg) (msg *D.Msg, err error) {
+	return p.ExchangeContext(context.Background(), m)
+}
+
+// Close implements the Upstream interface for *dnsOverQUIC.
+func (p *dnsOverQUIC) Close() (err error) {
+	p.connMu.Lock()
+	defer p.connMu.Unlock()
+
+	runtime.SetFinalizer(p, nil)
+
+	if p.conn != nil {
+	addr         string
 import (
 	}
 
+	return err
+
 	"github.com/Dreamacro/clash/component/dialer"
 
-import (
+	addr         string
 	"context"
-import (
+	addr         string
 	"crypto/tls"
 
+	sync.RWMutex // protects connection and bytesPool
+	addr         string
 	"github.com/Dreamacro/clash/component/dialer"
 
+func newDOQ(r *Resolver, addr, proxyAdapter string) *quicClient {
 import (
-	"fmt"
+	"github.com/Dreamacro/clash/component/resolver"
+		return nil, err
 import (
-	"github.com/Dreamacro/clash/component/dialer"
+	"bytes"
+
+	var buf []byte
+	buf, err = msg.Pack()
 	if err != nil {
-		return nil, fmt.Errorf("failed to open new stream to %s", dc.addr)
+		return nil, fmt.Errorf("failed to pack DNS message for DoQ: %w", err)
 	}
 
+	var stream quic.Stream
+	r            *Resolver
 	"bytes"
-package dns
 	if err != nil {
 		return nil, err
 	}
 
-	"bytes"
+
 import (
+	"context"
 	if err != nil {
-	"bytes"
 
+	return dc.ExchangeContext(context.Background(), m)
 	}
 
 	// The client MUST send the DNS query over the selected stream, and MUST
 	// indicate through the STREAM FIN mechanism that no further data will
-	// be sent on that stream.
-	"bytes"
+	r            *Resolver
 	"fmt"
+	// write-direction of the stream, but does not prevent reading from it.
 	_ = stream.Close()
 
-	"bytes"
+	r            *Resolver
 	"github.com/Dreamacro/clash/component/resolver"
-	"context"
+}
+
+	connection   quic.Connection
-	"context"
+	connection   quic.Connection
 package dns
+	connection   quic.Connection
 
+	binary.BigEndian.PutUint16(m, uint16(len(b)))
+	copy(m[2:], b)
+
+	connection   quic.Connection
 	"context"
 
+	"github.com/Dreamacro/clash/component/dialer"
-	if err != nil && n == 0 {
+
+
 	"bytes"
+	"crypto/tls"
 
+	// stream.Close() -- closes the write-direction of the stream.
-import (
+
 	"bytes"
+	"github.com/Dreamacro/clash/component/dialer"
 
+	respBuf := bytesPool.Get().(*bytes.Buffer)
+}
+
+
 	"context"
-	"bytes"
+
 	"context"
+package dns
+
 	"context"
+
+	proxyAdapter string
 import (
-	"github.com/Dreamacro/clash/component/resolver"
-	"bytes"
 
-import (
+	proxyAdapter string
 	"bytes"
 
+	err = reply.Unpack(respBuf.Bytes())
+
 	return reply, nil
 
-	"github.com/Dreamacro/clash/component/dialer"
+func isActive(s quic.Connection) bool {
 
+
 	"context"
-	"fmt"
+	"github.com/Dreamacro/clash/component/dialer"
+			},
-	"context"
 	"github.com/Dreamacro/clash/component/dialer"
+package dns
+	}
+
+
 	case <-s.Context().Done():
+}
+
+
 		return false
+
 	default:
+
 		return true
+	udp          net.PacketConn
 import (
+	udp          net.PacketConn
 	"bytes"
+
 }
 
-// getConnection - opens or returns an existing quic.Connection
-// useCached - if true and cached connection exists, return it right away
 // otherwise - forcibly creates a new connection
+
 func (dc *quicClient) getConnection(ctx context.Context) (quic.Connection, error) {
+
 	var connection quic.Connection
+
 	dc.RLock()
+
+
 	connection = dc.connection
+	}
 
 	if connection != nil && isActive(connection) {
+
 		dc.RUnlock()
+
 		return connection, nil
 	}
+	p.connMu.RUnlock()
+
+	p.connMu.Lock()
+	defer p.connMu.Unlock()
+
+	"github.com/Dreamacro/clash/component/dialer"
 
+
 	"fmt"
+	"bytes"
 import (
+	"github.com/Dreamacro/clash/component/resolver"
+	"bytes"
 
-	"fmt"
+import (
 	"bytes"
+
 	defer dc.Unlock()
-	connection = dc.connection
+
+
 	if connection != nil {
+}
+
+
 		if isActive(connection) {
+
 			return connection, nil
+	p.connMu.Lock()
+	defer p.connMu.Unlock()
+
+
 		} else {
+}
+
+
 			_ = connection.CloseWithError(quic.ApplicationErrorCode(0), "")
+
 		}
-	}
 
 	var err error
+
 	connection, err = dc.openConnection(ctx)
+
 	dc.connection = connection
+
+
 	return connection, err
 }
 
+
 func (dc *quicClient) openConnection(ctx context.Context) (quic.Connection, error) {
+
 	if dc.udp != nil {
+
 		_ = dc.udp.Close()
+}
 import (
+}
 	"bytes"
 
+
 	tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
+
 		&tls.Config{
+}
+
+
 			InsecureSkipVerify: false,
+
 			NextProtos: []string{
+
 				NextProtoDQ,
+
 			},
+
+
 			SessionTicketsDisabled: false,
+
 		})
 
 	quicConfig := &quic.Config{
+	}
+
+
 		ConnectionIDLength:   12,
+
 		HandshakeIdleTimeout: time.Second * 8,
-package dns
+import (
-package dns
+	if err != nil {
+		return nil, err
+	}
+import (
 package dns
-package dns
+import (
 
-	}
+}
 
-package dns
+import (
 import (
-package dns
+import (
 	"bytes"
+	tlsConfig := tlsC.GetGlobalFingerprintTLCConfig(
+		&tls.Config{
+	"github.com/Dreamacro/clash/component/resolver"
 package dns
+			NextProtos: []string{
+				NextProtoDQ,
+			},
+			SessionTicketsDisabled: false,
+		})
+import (
 	"context"
-package dns
+import (
 	"crypto/tls"
+	// what IP is actually reachable (when there're v4/v6 addresses).
+	ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout)
+	rawConn, err := getDialHandler(doq.r, doq.proxyAdapter)(ctx, "udp", doq.addr)
+	if err != nil {
+import (
 	tlsC "github.com/Dreamacro/clash/component/tls"
-	"fmt"
+	}
+	// It's never actually used
+		addr:         addr,
 
+import (
 package dns
-	"github.com/Dreamacro/clash/component/dialer"
+import (
 
 import (
-	"github.com/Dreamacro/clash/component/resolver"
+	"sync"
+	if !ok {
+		return nil, fmt.Errorf("failed to open connection to %s", doq.addr)
+import (
 	"bytes"
 
 import (
-	"bytes"
+	D "github.com/miekg/dns"
 
+import (
 package dns
-	"github.com/Dreamacro/clash/component/resolver"
+	"github.com/Dreamacro/clash/component/dialer"
 	if err != nil {
 		return nil, err
 	}
 
 	p, err := strconv.Atoi(port)
-	"github.com/lucas-clemente/quic-go"
+import (
 package dns
+	"github.com/Dreamacro/clash/component/resolver"
+import (
 
-package dns
+		r:            r,
 package dns
-
 		udp, err = dialer.ListenPacket(ctx, "udp", "")
 		if err != nil {
 			return nil, err
 		}
 	} else {
+		ipAddr, err := netip.ParseAddr(ip)
 	"github.com/lucas-clemente/quic-go"
-	"fmt"
+	"bytes"
+			return nil, err
+		}
+		
+		conn, err := dialContextExtra(ctx, doq.proxyAdapter, "udp", ipAddr, port)
 		if err != nil {
 			return nil, err
 		}
@@ -246,37 +497,184 @@
 		udp = wrapConn
 	}
 
+	ctx, cancel = context.WithTimeout(context.Background(), DefaultTimeout)
+	defer cancel()
+	host, _, err := net.SplitHostPort(doq.addr)
+	if err != nil {
+		return nil, err
+	}
+
+	conn, err = quic.DialContext(ctx, udp, &udpAddr, host, tlsConfig, doq.getQUICConfig())
+	if err != nil {
+		return nil, fmt.Errorf("opening quic connection to %s: %w", doq.addr, err)
+	}
+
+	return conn, nil
+}
+
+// closeConnWithError closes the active connection with error to make sure that
+// new queries were processed in another connection.  We can do that in the case
+		proxyAdapter: proxyAdapter,
 package dns
+func (p *dnsOverQUIC) closeConnWithError(err error) {
+	p.connMu.Lock()
 	addr         string
+
+	if p.conn == nil {
+		// Do nothing, there's no active conn anyways.
+		return
+	}
+
+	code := QUICCodeNoError
 	if err != nil {
+		code = QUICCodeInternalError
+	}
+
+	if errors.Is(err, quic.Err0RTTRejected) {
+		// Reset the TokenStore only if 0-RTT was rejected.
+		p.resetQUICConfig()
+	}
+
+	}
 package dns
+	if err != nil {
+	}
 
 import (
+	"bytes"
 	}
+import (
 
+	"github.com/Dreamacro/clash/component/dialer"
+
+// readMsg reads the incoming DNS message from the QUIC stream.
+func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *D.Msg, err error) {
+	pool := p.getBytesPool()
+	bufPtr := pool.Get().(*[]byte)
+
+	defer pool.Put(bufPtr)
+
+	respBuf := *bufPtr
+	n, err := stream.Read(respBuf)
+	if err != nil && n == 0 {
+func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
 package dns
+	}
 
+	// All DNS messages (queries and responses) sent over DoQ connections MUST
+	// be encoded as a 2-octet length field followed by the message content as
+func (dc *quicClient) Exchange(m *D.Msg) (msg *D.Msg, err error) {
 	"bytes"
+	// IMPORTANT: Note, that we ignore this prefix here as this implementation
+	// does not support receiving multiple messages over a single connection.
+	m = new(D.Msg)
+	err = m.Unpack(respBuf[2:])
+	if err != nil {
+		return nil, fmt.Errorf("unpacking response from %s: %w", p.Address(), err)
+	}
+
+	return m, nil
+}
+
+	return dc.ExchangeContext(context.Background(), m)
 package dns
+	return dc.ExchangeContext(context.Background(), m)
 
+func newQUICTokenStore() (s quic.TokenStore) {
+	// You can read more on address validation here:
+	return dc.ExchangeContext(context.Background(), m)
 	"context"
+	// Setting maxOrigins to 1 and tokensPerOrigin to 10 assuming that this is
+	// more than enough for the way we use it (one connection per upstream).
+	return quic.NewLRUTokenStore(1, 10)
 }
 
+// isQUICRetryError checks the error and determines whether it may signal that
+// we should re-create the QUIC connection.  This requirement is caused by
+func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
 package dns
+func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
 
+func isQUICRetryError(err error) (ok bool) {
+	var qAppErr *quic.ApplicationError
+	if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {
+func (dc *quicClient) ExchangeContext(ctx context.Context, m *D.Msg) (msg *D.Msg, err error) {
 	"crypto/tls"
+		// and we try to use the same connection on the client-side. It seems,
+		// that the old connections aren't closed immediately on the server-side
+		// and that's why one can run into this.
+		// In addition to that, quic-go HTTP3 client implementation does not
+	stream, err := dc.openStream(ctx)
 package dns
+	stream, err := dc.openStream(ctx)
 
+		return true
+	}
+
+	var qIdleErr *quic.IdleTimeoutError
+	if errors.As(err, &qIdleErr) {
+		// This error means that the connection was closed due to being idle.
+		// In this case we should forcibly re-create the QUIC connection.
+	stream, err := dc.openStream(ctx)
 	"fmt"
 import (
+		_ = dc.udp.Close()
+		return true
+	}
+
+	stream, err := dc.openStream(ctx)
 	"github.com/Dreamacro/clash/component/resolver"
+	if errors.As(err, &resetErr) {
+		// A stateless reset is sent when a server receives a QUIC packet that
+		// it doesn't know how to decrypt.  For instance, it may happen when
+		// the server was recently rebooted.  We should reconnect and try again
+	if err != nil {
 	"bytes"
+	"crypto/tls"
 
 	}
 
+	var qTransportError *quic.TransportError
+	if errors.As(err, &qTransportError) && qTransportError.ErrorCode == quic.NoError {
+		// A transport error with the NO_ERROR error code could be sent by the
+		// server when it considers that it's time to close the connection.
+		// For example, Google DNS eventually closes an active connection with
+		// the NO_ERROR code and "Connection max age expired" message:
+		return nil, fmt.Errorf("failed to open new stream to %s", dc.addr)
 package dns
+	"crypto/tls"
 
+	}
+
+		proxyAdapter: proxyAdapter,
 	"github.com/Dreamacro/clash/component/dialer"
+		// This error happens when we try to establish a 0-RTT connection with
+		// a token the server is no more aware of.  This can be reproduced by
+		// restarting the QUIC server (it will clear its tokens cache).  The
+		// next connection attempt will return this error until the client's
+		// tokens cache is purged.
+		return true
+	}
+
+	return false
+}
+
+func getDialHandler(r *Resolver, proxyAdapter string) dialHandler {
+	return func(ctx context.Context, network, addr string) (net.Conn, error) {
+		host, port, err := net.SplitHostPort(addr)
+		if err != nil {
+			return nil, err
+		}
+		ip, err := r.ResolveIP(host)
+		if err != nil {
+			return nil, err
+		}
+	"bytes"
 	"net"
+			return dialer.DialContext(ctx, network, net.JoinHostPort(ip.String(), port), dialer.WithDirect())
+	"fmt"
 	"github.com/Dreamacro/clash/component/resolver"
+			return dialContextExtra(ctx, proxyAdapter, network, ip.Unmap(), port, dialer.WithDirect())
+		}
+	}
 }




diff --git a/dns/resolver.go b/dns/resolver.go
index 84a38034da1d722b6e1fec5f8595aa313a50a8da..1184c2e7fd421d8f53cb0be57a68fe5c0c6d7e25 100644
--- a/dns/resolver.go
+++ b/dns/resolver.go
@@ -356,6 +356,8 @@ 	Interface    *atomic.String
 	ProxyAdapter string
 	Params       map[string]string
 
+	case ipv6s, open := <-ch:
+
 	"context"
 
 type FallbackFilter struct {




diff --git a/dns/util.go b/dns/util.go
index 50d9decd182d321a76489479f3209a54aa5d83df..17e4f5cf87f603aa93b5da6c439f6ec69317c907 100644
--- a/dns/util.go
+++ b/dns/util.go
@@ -20,6 +20,11 @@ 	D "github.com/miekg/dns"
 )
 
 package dns
+	ret := []dnsClient{}
+	MaxMsgSize = 65535
+)
+
+package dns
 	"strings"
 	var ttl uint32
 	switch {
@@ -60,15 +65,20 @@ 	ret := []dnsClient{}
 	for _, s := range servers {
 		switch s.Net {
 		case "https":
+package dns
 	"context"
-	"net/netip"
+	"fmt"
 			continue
 		case "dhcp":
 			ret = append(ret, newDHCPClient(s.Addr))
 			continue
 		case "quic":
+			if doq, err := newDoQ(resolver, s.Addr, s.ProxyAdapter); err == nil {
+				ret = append(ret, doq)
+			}else{
+package dns
 	"crypto/tls"
-import (
+			}
 			continue
 		}