From a03a56c89026753989a4b06787ef1a08dfbabb9c Mon Sep 17 00:00:00 2001
From: Andrey Meshkov <am@adguard.com>
Date: Tue, 20 Sep 2022 15:38:01 +0300
Subject: [PATCH] Pull request: proxy: added HTTP/3 support to the
 DNS-over-HTTPS server implementation

Merge in DNS/dnsproxy from doh3server to master

Squashed commit of the following:

commit dd7f6ecb0264afd16ee6fcd47ff7bafe06797645
Author: Andrey Meshkov <am@adguard.com>
Date:   Tue Sep 20 14:17:51 2022 +0300

    upstream: fix review comments

commit 3b887f614163f4900f75807c990ad2a5d354d3b5
Author: Andrey Meshkov <am@adguard.com>
Date:   Tue Sep 20 00:14:19 2022 +0300

    proxy: added address validation logic

commit b29dc3c3b6746ad5be921941904f16ab228b1dab
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Sep 19 23:31:21 2022 +0300

    proxy: fix review comments, general improvements

commit 79f47f54adcd30a68a9f7bc0111025ae0a32d99d
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Sep 19 20:43:26 2022 +0300

    upstream: several improvements in DoH3 and DoQ upstreams

    The previous implementation weren't able to properly handle a situation when the
    server was restarted. This commit greatly improves the overall stability.

commit 59cf92b6097d78acf6f088057134888993f7ca43
Author: Andrey Meshkov <am@adguard.com>
Date:   Sat Sep 17 02:51:40 2022 +0300

    proxy: remoteAddr for DoH depends on HTTP version now

commit 804ddedd2807870b7d36dae5ce9857de3a7f7286
Author: Andrey Meshkov <am@adguard.com>
Date:   Sat Sep 17 01:53:32 2022 +0300

    proxy: added HTTP/3 support to the DNS-over-HTTPS server implementation
    The implementation follows the old approach that was used in dnsproxy, i.e. it
    adds another bunch of "listeners", the new ones are for HTTP/3. HTTP/3 support
    is not enabled by default, it should be enabled explicitly by setting HTTP3
    field of proxy.Config to true.

    The "--http3" command-line argument now controls DoH3 support on both the
    client-side and the server-side.

    There's one more important change that was made while refactoring the code.
    Previously, we were creating a separate http.Server instance for every listen
    address that's used. It is unclear to me what's the reason for that since a
    single instance can be used to serve on every address. This mistake is fixed
    now.
---
 README.md                                     |   5 +
 fastip/fastest.go                             |   3 +-
 fastip/ping_test.go                           |   6 +-
 go.mod                                        |   1 +
 go.sum                                        |   2 +
 main.go                                       |   4 +-
 proxy/config.go                               |   1 +
 proxy/proxy.go                                | 107 ++++-----
 proxy/proxy_test.go                           |   8 +-
 proxy/server.go                               |   9 +-
 proxy/server_dnscrypt.go                      |  23 +-
 proxy/server_https.go                         | 130 +++++++----
 proxy/server_https_test.go                    | 194 +++++++++++-----
 proxy/server_quic.go                          | 123 +++++++++--
 proxy/server_quic_test.go                     |   2 +-
 upstream/bootstrap.go                         |  13 ++
 upstream/upstream_doh.go                      | 120 ++++++++--
 upstream/upstream_doh_test.go                 | 103 ++++++++-
 upstream/upstream_quic.go                     | 207 ++++++++++++------
 upstream/upstream_quic_test.go                | 164 +++++++++++++-
 vendor/github.com/bluele/gcache/LICENSE       | Bin 0 -> 1077 bytes
 vendor/github.com/bluele/gcache/README.md     | Bin 0 -> 5285 bytes
 vendor/github.com/bluele/gcache/arc.go        | Bin 0 -> 9165 bytes
 vendor/github.com/bluele/gcache/cache.go      | Bin 0 -> 5189 bytes
 vendor/github.com/bluele/gcache/clock.go      | Bin 0 -> 800 bytes
 vendor/github.com/bluele/gcache/lfu.go        | Bin 0 -> 8171 bytes
 vendor/github.com/bluele/gcache/lru.go        | Bin 0 -> 6631 bytes
 vendor/github.com/bluele/gcache/simple.go     | Bin 0 -> 6402 bytes
 .../github.com/bluele/gcache/singleflight.go  | Bin 0 -> 2117 bytes
 vendor/github.com/bluele/gcache/stats.go      | Bin 0 -> 1003 bytes
 vendor/github.com/bluele/gcache/utils.go      | Bin 0 -> 149 bytes
 vendor/modules.txt                            | Bin 6580 -> 6660 bytes
 32 files changed, 931 insertions(+), 294 deletions(-)
 create mode 100644 vendor/github.com/bluele/gcache/LICENSE
 create mode 100644 vendor/github.com/bluele/gcache/README.md
 create mode 100644 vendor/github.com/bluele/gcache/arc.go
 create mode 100644 vendor/github.com/bluele/gcache/cache.go
 create mode 100644 vendor/github.com/bluele/gcache/clock.go
 create mode 100644 vendor/github.com/bluele/gcache/lfu.go
 create mode 100644 vendor/github.com/bluele/gcache/lru.go
 create mode 100644 vendor/github.com/bluele/gcache/simple.go
 create mode 100644 vendor/github.com/bluele/gcache/singleflight.go
 create mode 100644 vendor/github.com/bluele/gcache/stats.go
 create mode 100644 vendor/github.com/bluele/gcache/utils.go

diff --git a/README.md b/README.md
index ff36c26c..425258f0 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,11 @@ Runs a DNS-over-HTTPS proxy on `127.0.0.1:443`.
 ./dnsproxy -l 127.0.0.1 --https-port=443 --tls-crt=example.crt --tls-key=example.key -u 8.8.8.8:53 -p 0
 ```
 
+Runs a DNS-over-HTTPS proxy on `127.0.0.1:443` with HTTP/3 support.
+```shell
+./dnsproxy -l 127.0.0.1 --https-port=443 --http3 --tls-crt=example.crt --tls-key=example.key -u 8.8.8.8:53 -p 0
+```
+
 Runs a DNS-over-QUIC proxy on `127.0.0.1:853`.
 ```shell
 ./dnsproxy -l 127.0.0.1 --quic-port=853 --tls-crt=example.crt --tls-key=example.key -u 8.8.8.8:53 -p 0
diff --git a/fastip/fastest.go b/fastip/fastest.go
index 685cbd32..c30d4333 100644
--- a/fastip/fastest.go
+++ b/fastip/fastest.go
@@ -6,11 +6,10 @@ import (
 	"sync"
 	"time"
 
-	"github.com/AdguardTeam/golibs/log"
-
 	"github.com/AdguardTeam/dnsproxy/proxyutil"
 	"github.com/AdguardTeam/dnsproxy/upstream"
 	"github.com/AdguardTeam/golibs/cache"
+	"github.com/AdguardTeam/golibs/log"
 	"github.com/miekg/dns"
 )
 
diff --git a/fastip/ping_test.go b/fastip/ping_test.go
index 23fc851e..be1cf260 100644
--- a/fastip/ping_test.go
+++ b/fastip/ping_test.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/AdguardTeam/golibs/netutil"
+	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
@@ -99,7 +100,7 @@ func TestFastestAddr_PingAll_cache(t *testing.T) {
 	t.Run("not_cached", func(t *testing.T) {
 		listener, err := net.Listen("tcp", ":0")
 		require.NoError(t, err)
-		t.Cleanup(func() { require.NoError(t, listener.Close()) })
+		testutil.CleanupAndRequireSuccess(t, listener.Close)
 
 		ip := net.IP{127, 0, 0, 1}
 		f := NewFastestAddr()
@@ -138,8 +139,7 @@ func listen(t *testing.T, ip net.IP) (port uint) {
 
 	l, err := net.Listen("tcp", netutil.IPPort{IP: ip, Port: 0}.String())
 	require.NoError(t, err)
-
-	t.Cleanup(func() { require.NoError(t, l.Close()) })
+	testutil.CleanupAndRequireSuccess(t, l.Close)
 
 	return uint(l.Addr().(*net.TCPAddr).Port)
 }
diff --git a/go.mod b/go.mod
index 52232f05..6485dd61 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/ameshkov/dnscrypt/v2 v2.2.5
 	github.com/ameshkov/dnsstamps v1.0.3
 	github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0
+	github.com/bluele/gcache v0.0.2
 	github.com/jessevdk/go-flags v1.5.0
 	github.com/lucas-clemente/quic-go v0.29.0
 	github.com/miekg/dns v1.1.50
diff --git a/go.sum b/go.sum
index 484a8304..1bff13de 100644
--- a/go.sum
+++ b/go.sum
@@ -10,6 +10,8 @@ github.com/ameshkov/dnsstamps v1.0.3 h1:Srzik+J9mivH1alRACTbys2xOxs0lRH9qnTA7Y1O
 github.com/ameshkov/dnsstamps v1.0.3/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
 github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 h1:0b2vaepXIfMsG++IsjHiI2p4bxALD1Y2nQKGMR5zDQM=
 github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
+github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
+github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
diff --git a/main.go b/main.go
index df92091f..6474e2c9 100644
--- a/main.go
+++ b/main.go
@@ -81,8 +81,7 @@ type Options struct {
 	DNSCryptConfigPath string `yaml:"dnscrypt-config" short:"g" long:"dnscrypt-config" description:"Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt"`
 
 	// HTTP3 controls whether HTTP/3 is enabled for this instance of dnsproxy.
-	// At this point it only enables it for upstreams, but in the future it will
-	// also enable it for the server.
+	// It enables HTTP/3 support for both the DoH upstreams and the DoH server.
 	HTTP3 bool `yaml:"http3" long:"http3" description:"Enable HTTP/3 support" optional:"yes" optional-value:"false"`
 
 	// Upstream DNS servers settings
@@ -274,6 +273,7 @@ func createProxyConfig(options *Options) proxy.Config {
 		CacheMaxTTL:     options.CacheMaxTTL,
 		CacheOptimistic: options.CacheOptimistic,
 		RefuseAny:       options.RefuseAny,
+		HTTP3:           options.HTTP3,
 		// TODO(e.burkov):  The following CIDRs are aimed to match any
 		// address.  This is not quite proper approach to be used by
 		// default so think about configuring it.
diff --git a/proxy/config.go b/proxy/config.go
index 0e10bdd9..f9e1bfe4 100644
--- a/proxy/config.go
+++ b/proxy/config.go
@@ -54,6 +54,7 @@ type Config struct {
 	// --
 
 	TLSConfig            *tls.Config    // necessary for TLS, HTTPS, QUIC
+	HTTP3                bool           // if true, HTTPS server will also support HTTP/3
 	DNSCryptProviderName string         // DNSCrypt provider name
 	DNSCryptResolverCert *dnscrypt.Cert // DNSCrypt resolver certificate
 
diff --git a/proxy/proxy.go b/proxy/proxy.go
index 816a26a8..8528ab9d 100644
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -4,6 +4,7 @@ package proxy
 
 import (
 	"fmt"
+	"io"
 	"net"
 	"net/http"
 	"sync"
@@ -18,6 +19,7 @@ import (
 	"github.com/AdguardTeam/golibs/netutil"
 	"github.com/ameshkov/dnscrypt/v2"
 	"github.com/lucas-clemente/quic-go"
+	"github.com/lucas-clemente/quic-go/http3"
 	"github.com/miekg/dns"
 	gocache "github.com/patrickmn/go-cache"
 )
@@ -65,15 +67,17 @@ type Proxy struct {
 	// Listeners
 	// --
 
-	udpListen         []*net.UDPConn   // UDP listen connections
-	tcpListen         []net.Listener   // TCP listeners
-	tlsListen         []net.Listener   // TLS listeners
-	quicListen        []quic.Listener  // QUIC listeners
-	httpsListen       []net.Listener   // HTTPS listeners
-	httpsServer       []*http.Server   // HTTPS server instance
-	dnsCryptUDPListen []*net.UDPConn   // UDP listen connections for DNSCrypt
-	dnsCryptTCPListen []net.Listener   // TCP listeners for DNSCrypt
-	dnsCryptServer    *dnscrypt.Server // DNSCrypt server instance
+	udpListen         []*net.UDPConn       // UDP listen connections
+	tcpListen         []net.Listener       // TCP listeners
+	tlsListen         []net.Listener       // TLS listeners
+	quicListen        []quic.EarlyListener // QUIC listeners
+	httpsListen       []net.Listener       // HTTPS listeners
+	httpsServer       *http.Server         // HTTPS server instance
+	h3Listen          []quic.EarlyListener // HTTP/3 listeners
+	h3Server          *http3.Server        // HTTP/3 server instance
+	dnsCryptUDPListen []*net.UDPConn       // UDP listen connections for DNSCrypt
+	dnsCryptTCPListen []net.Listener       // TCP listeners for DNSCrypt
+	dnsCryptServer    *dnscrypt.Server     // DNSCrypt server instance
 
 	// Upstream
 	// --
@@ -145,19 +149,6 @@ func (p *Proxy) Init() (err error) {
 		p.requestGoroutinesSema = newNoopSemaphore()
 	}
 
-	if p.DNSCryptResolverCert != nil && p.DNSCryptProviderName != "" {
-		log.Info("Initializing DNSCrypt: %s", p.DNSCryptProviderName)
-		p.dnsCryptServer = &dnscrypt.Server{
-			ProviderName: p.DNSCryptProviderName,
-			ResolverCert: p.DNSCryptResolverCert,
-			Handler: &dnsCryptHandler{
-				proxy: p,
-
-				requestGoroutinesSema: p.requestGoroutinesSema,
-			},
-		}
-	}
-
 	p.udpOOBSize = proxyutil.UDPGetOOBSize()
 	p.bytesPool = &sync.Pool{
 		New: func() interface{} {
@@ -212,6 +203,17 @@ func (p *Proxy) Start() (err error) {
 	return nil
 }
 
+// closeAll closes all elements in the toClose slice and if there's any error
+// appends it to the errs slice.
+func closeAll[T io.Closer](toClose []T, errs *[]error) {
+	for _, c := range toClose {
+		err := c.Close()
+		if err != nil {
+			*errs = append(*errs, err)
+		}
+	}
+}
+
 // Stop stops the proxy server including all its listeners
 func (p *Proxy) Stop() error {
 	log.Info("Stopping the DNS proxy server")
@@ -225,61 +227,38 @@ func (p *Proxy) Stop() error {
 
 	errs := []error{}
 
-	for _, l := range p.tcpListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing tcp listening socket: %w", err))
-		}
-	}
+	closeAll(p.tcpListen, &errs)
 	p.tcpListen = nil
 
-	for _, l := range p.udpListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing udp listening socket: %w", err))
-		}
-	}
+	closeAll(p.udpListen, &errs)
 	p.udpListen = nil
 
-	for _, l := range p.tlsListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing tls listening socket: %w", err))
-		}
-	}
+	closeAll(p.tlsListen, &errs)
 	p.tlsListen = nil
 
-	for _, srv := range p.httpsServer {
-		err := srv.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing https server: %w", err))
-		}
+	if p.httpsServer != nil {
+		closeAll([]io.Closer{p.httpsServer}, &errs)
+		p.httpsServer = nil
+
+		// No need to close these since they're closed by httpsServer.Close().
+		p.httpsListen = nil
 	}
-	p.httpsListen = nil
-	p.httpsServer = nil
 
-	for _, l := range p.quicListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing quic listener: %w", err))
-		}
+	if p.h3Server != nil {
+		closeAll([]io.Closer{p.h3Server}, &errs)
+		p.h3Server = nil
 	}
+
+	closeAll(p.h3Listen, &errs)
+	p.h3Listen = nil
+
+	closeAll(p.quicListen, &errs)
 	p.quicListen = nil
 
-	for _, l := range p.dnsCryptUDPListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing dnscrypt udp listening socket: %w", err))
-		}
-	}
+	closeAll(p.dnsCryptUDPListen, &errs)
 	p.dnsCryptUDPListen = nil
 
-	for _, l := range p.dnsCryptTCPListen {
-		err := l.Close()
-		if err != nil {
-			errs = append(errs, fmt.Errorf("closing dnscrypt tcp listening socket: %w", err))
-		}
-	}
+	closeAll(p.dnsCryptTCPListen, &errs)
 	p.dnsCryptTCPListen = nil
 
 	p.started = false
diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go
index e68cd972..6596d131 100644
--- a/proxy/proxy_test.go
+++ b/proxy/proxy_test.go
@@ -736,9 +736,7 @@ func TestResponseInRequest(t *testing.T) {
 func TestNoQuestion(t *testing.T) {
 	dnsProxy := createTestProxy(t, nil)
 	require.NoError(t, dnsProxy.Start())
-	t.Cleanup(func() {
-		require.NoError(t, dnsProxy.Stop())
-	})
+	testutil.CleanupAndRequireSuccess(t, dnsProxy.Stop)
 
 	addr := dnsProxy.Addr(ProtoUDP)
 	client := &dns.Client{Net: "udp", Timeout: 500 * time.Millisecond}
@@ -780,9 +778,7 @@ func (wu *funcUpstream) Address() string {
 func TestProxy_ReplyFromUpstream_badResponse(t *testing.T) {
 	dnsProxy := createTestProxy(t, nil)
 	require.NoError(t, dnsProxy.Start())
-	t.Cleanup(func() {
-		require.NoError(t, dnsProxy.Stop())
-	})
+	testutil.CleanupAndRequireSuccess(t, dnsProxy.Stop)
 
 	exchangeFunc := func(m *dns.Msg) (resp *dns.Msg, err error) {
 		resp = &dns.Msg{}
diff --git a/proxy/server.go b/proxy/server.go
index e9fc66c7..a406b4ae 100644
--- a/proxy/server.go
+++ b/proxy/server.go
@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/AdguardTeam/golibs/log"
+	"github.com/lucas-clemente/quic-go"
 	"github.com/miekg/dns"
 )
 
@@ -53,8 +54,12 @@ func (p *Proxy) startListeners() error {
 		go p.tcpPacketLoop(l, ProtoTLS, p.requestGoroutinesSema)
 	}
 
-	for i := range p.httpsServer {
-		go p.listenHTTPS(p.httpsServer[i], p.httpsListen[i])
+	for _, l := range p.httpsListen {
+		go func(l net.Listener) { _ = p.httpsServer.Serve(l) }(l)
+	}
+
+	for _, l := range p.h3Listen {
+		go func(l quic.EarlyListener) { _ = p.h3Server.ServeListener(l) }(l)
 	}
 
 	for _, l := range p.quicListen {
diff --git a/proxy/server_dnscrypt.go b/proxy/server_dnscrypt.go
index 308d068a..afc7b68f 100644
--- a/proxy/server_dnscrypt.go
+++ b/proxy/server_dnscrypt.go
@@ -4,12 +4,33 @@ import (
 	"fmt"
 	"net"
 
+	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/ameshkov/dnscrypt/v2"
 	"github.com/miekg/dns"
 )
 
-func (p *Proxy) createDNSCryptListeners() error {
+func (p *Proxy) createDNSCryptListeners() (err error) {
+	if len(p.DNSCryptUDPListenAddr) == 0 && len(p.DNSCryptTCPListenAddr) == 0 {
+		// Do nothing if DNSCrypt listen addresses are not specified.
+		return nil
+	}
+
+	if p.DNSCryptResolverCert == nil || p.DNSCryptProviderName == "" {
+		return errors.Error("invalid DNSCrypt configuration: no certificate or provider name")
+	}
+
+	log.Info("Initializing DNSCrypt: %s", p.DNSCryptProviderName)
+	p.dnsCryptServer = &dnscrypt.Server{
+		ProviderName: p.DNSCryptProviderName,
+		ResolverCert: p.DNSCryptResolverCert,
+		Handler: &dnsCryptHandler{
+			proxy: p,
+
+			requestGoroutinesSema: p.requestGoroutinesSema,
+		},
+	}
+
 	for _, a := range p.DNSCryptUDPListenAddr {
 		log.Info("Creating a DNSCrypt UDP listener")
 		udpListen, err := net.ListenUDP("udp", a)
diff --git a/proxy/server_https.go b/proxy/server_https.go
index 7324fc14..0acad3e2 100644
--- a/proxy/server_https.go
+++ b/proxy/server_https.go
@@ -1,6 +1,7 @@
 package proxy
 
 import (
+	"crypto/tls"
 	"encoding/base64"
 	"fmt"
 	"io"
@@ -11,56 +12,107 @@ import (
 
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/AdguardTeam/golibs/netutil"
+	"github.com/lucas-clemente/quic-go"
+	"github.com/lucas-clemente/quic-go/http3"
 	"github.com/miekg/dns"
 	"golang.org/x/net/http2"
 )
 
-func (p *Proxy) createHTTPSListeners() error {
-	for _, a := range p.HTTPSListenAddr {
-		log.Info("Creating an HTTPS server")
-		tcpListen, err := net.ListenTCP("tcp", a)
-		if err != nil {
-			return fmt.Errorf("starting https listener: %w", err)
-		}
-		p.httpsListen = append(p.httpsListen, tcpListen)
-		log.Info("Listening to https://%s", tcpListen.Addr())
+// listenHTTP creates instances of TLS listeners that will be used to run an
+// H1/H2 server.  Returns the address the listener actually listens to (useful
+// in the case if port 0 is specified).
+func (p *Proxy) listenHTTP(addr *net.TCPAddr) (laddr *net.TCPAddr, err error) {
+	tcpListen, err := net.ListenTCP("tcp", addr)
+	if err != nil {
+		return nil, fmt.Errorf("tcp listener: %w", err)
+	}
+	log.Info("Listening to https://%s", tcpListen.Addr())
 
-		tlsConfig := p.TLSConfig.Clone()
-		tlsConfig.NextProtos = []string{http2.NextProtoTLS, "http/1.1"}
+	tlsConfig := p.TLSConfig.Clone()
+	tlsConfig.NextProtos = []string{http2.NextProtoTLS, "http/1.1"}
 
-		srv := &http.Server{
-			TLSConfig:         tlsConfig,
-			Handler:           p,
-			ReadHeaderTimeout: defaultTimeout,
-			WriteTimeout:      defaultTimeout,
-		}
+	tlsListen := tls.NewListener(tcpListen, tlsConfig)
+	p.httpsListen = append(p.httpsListen, tlsListen)
 
-		p.httpsServer = append(p.httpsServer, srv)
+	return tcpListen.Addr().(*net.TCPAddr), nil
+}
+
+// listenH3 creates instances of QUIC listeners that will be used for running
+// an HTTP/3 server.
+func (p *Proxy) listenH3(addr *net.UDPAddr) (err error) {
+	tlsConfig := p.TLSConfig.Clone()
+	tlsConfig.NextProtos = []string{"h3"}
+	quicListen, err := quic.ListenAddrEarly(addr.String(), tlsConfig, newServerQUICConfig())
+	if err != nil {
+		return fmt.Errorf("quic listener: %w", err)
 	}
+	log.Info("Listening to h3://%s", quicListen.Addr())
+
+	p.h3Listen = append(p.h3Listen, quicListen)
 
 	return nil
 }
 
-// serveHttps starts the HTTPS server
-func (p *Proxy) listenHTTPS(srv *http.Server, l net.Listener) {
-	log.Info("Listening to DNS-over-HTTPS on %s", l.Addr())
-	err := srv.ServeTLS(l, "", "")
+// createHTTPSListeners creates TCP/UDP listeners and HTTP/H3 servers.
+func (p *Proxy) createHTTPSListeners() (err error) {
+	p.httpsServer = &http.Server{
+		Handler: &proxyHTTPHandler{
+			proxy: p,
+			h3:    false,
+		},
+		ReadHeaderTimeout: defaultTimeout,
+		WriteTimeout:      defaultTimeout,
+	}
+
+	if p.HTTP3 {
+		p.h3Server = &http3.Server{
+			Handler: &proxyHTTPHandler{
+				proxy: p,
+				h3:    true,
+			},
+		}
+	}
+
+	for _, addr := range p.HTTPSListenAddr {
+		log.Info("Creating an HTTPS server")
 
-	if err != http.ErrServerClosed {
-		log.Info("HTTPS server was closed unexpectedly: %s", err)
-	} else {
-		log.Info("HTTPS server was closed")
+		tcpAddr, err := p.listenHTTP(addr)
+		if err != nil {
+			return fmt.Errorf("failed to start HTTPS server on %s: %w", addr, err)
+		}
+
+		if p.HTTP3 {
+			// HTTP/3 server listens to the same pair IP:port as the one HTTP/2
+			// server listens to.
+			udpAddr := &net.UDPAddr{IP: tcpAddr.IP, Port: tcpAddr.Port}
+			err = p.listenH3(udpAddr)
+			if err != nil {
+				return fmt.Errorf("failed to start HTTP/3 server on %s: %w", udpAddr, err)
+			}
+		}
 	}
+
+	return nil
+}
+
+// proxyHTTPHandler implements http.Handler and processes DoH queries.
+type proxyHTTPHandler struct {
+	// h3 is true if this is an HTTP/3 requests handler.
+	h3    bool
+	proxy *Proxy
 }
 
-// ServeHTTP is the http.RequestHandler implementation that handles DoH queries
+// type check
+var _ http.Handler = &proxyHTTPHandler{}
+
+// ServeHTTP is the http.Handler implementation that handles DoH queries.
 // Here is what it returns:
 //
 //   - http.StatusBadRequest if there is no DNS request data;
 //   - http.StatusUnsupportedMediaType if request content type is not
 //     "application/dns-message";
 //   - http.StatusMethodNotAllowed if request method is not GET or POST.
-func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+func (h *proxyHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	log.Tracef("Incoming HTTPS request on %s", r.URL)
 
 	var buf []byte
@@ -103,12 +155,12 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	addr, prx, err := remoteAddr(r)
+	addr, prx, err := remoteAddr(r, h.h3)
 	if err != nil {
 		log.Debug("warning: getting real ip: %s", err)
 	}
 
-	d := p.newDNSContext(ProtoHTTPS, req)
+	d := h.proxy.newDNSContext(ProtoHTTPS, req)
 	d.Addr = addr
 	d.HTTPRequest = r
 	d.HTTPResponseWriter = w
@@ -116,13 +168,13 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	if prx != nil {
 		ip, _ := netutil.IPAndPortFromAddr(prx)
 		log.Debug("request came from proxy server %s", prx)
-		if !p.proxyVerifier.Contains(ip) {
+		if !h.proxy.proxyVerifier.Contains(ip) {
 			log.Debug("proxy %s is not trusted, using original remote addr", ip)
 			d.Addr = prx
 		}
 	}
 
-	err = p.handleDNSRequest(d)
+	err = h.proxy.handleDNSRequest(d)
 	if err != nil {
 		log.Tracef("error handling DNS (%s) request: %s", d.Proto, err)
 	}
@@ -187,7 +239,7 @@ func realIPFromHdrs(r *http.Request) (realIP net.IP) {
 
 // remoteAddr returns the real client's address and the IP address of the latest
 // proxy server if any.
-func remoteAddr(r *http.Request) (addr, prx net.Addr, err error) {
+func remoteAddr(r *http.Request, h3 bool) (addr, prx net.Addr, err error) {
 	var hostStr, portStr string
 	if hostStr, portStr, err = net.SplitHostPort(r.RemoteAddr); err != nil {
 		return nil, nil, err
@@ -206,13 +258,15 @@ func remoteAddr(r *http.Request) (addr, prx net.Addr, err error) {
 	if realIP := realIPFromHdrs(r); realIP != nil {
 		log.Tracef("Using IP address from HTTP request: %s", realIP)
 
-		// TODO(a.garipov): Use net.UDPAddr here and below when
-		// necessary when we start supporting HTTP/3.
-		//
 		// TODO(a.garipov): Add port if we can get it from headers like
 		// X-Real-Port, X-Forwarded-Port, etc.
-		addr = &net.TCPAddr{IP: realIP, Port: 0}
-		prx = &net.TCPAddr{IP: host, Port: port}
+		if h3 {
+			addr = &net.UDPAddr{IP: realIP, Port: 0}
+			prx = &net.UDPAddr{IP: host, Port: port}
+		} else {
+			addr = &net.TCPAddr{IP: realIP, Port: 0}
+			prx = &net.TCPAddr{IP: host, Port: port}
+		}
 
 		return addr, prx, nil
 	}
diff --git a/proxy/server_https_test.go b/proxy/server_https_test.go
index 5685f188..68d14289 100644
--- a/proxy/server_https_test.go
+++ b/proxy/server_https_test.go
@@ -5,6 +5,7 @@ import (
 	"context"
 	"crypto/tls"
 	"crypto/x509"
+	"fmt"
 	"io"
 	"net"
 	"net/http"
@@ -12,12 +13,53 @@ import (
 	"testing"
 
 	"github.com/AdguardTeam/golibs/netutil"
+	"github.com/AdguardTeam/golibs/testutil"
+	"github.com/lucas-clemente/quic-go"
+	"github.com/lucas-clemente/quic-go/http3"
 	"github.com/miekg/dns"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 func TestHttpsProxy(t *testing.T) {
+	testCases := []struct {
+		name  string
+		http3 bool
+	}{{
+		name:  "https_proxy",
+		http3: false,
+	}, {
+		name:  "h3_proxy",
+		http3: true,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Prepare dnsProxy with its configuration.
+			tlsConf, caPem := createServerTLSConfig(t)
+			dnsProxy := createTestProxy(t, tlsConf)
+			dnsProxy.HTTP3 = tc.http3
+
+			// Run the proxy.
+			err := dnsProxy.Start()
+			require.NoError(t, err)
+			testutil.CleanupAndRequireSuccess(t, dnsProxy.Stop)
+
+			// Create the HTTP client that we'll be using for this test.
+			client := createTestHTTPClient(dnsProxy, caPem, tc.http3)
+
+			// Prepare a test message to be sent to the server.
+			msg := createTestMessage()
+
+			// Send the test message and check if the response is what we
+			// expected.
+			resp := sendTestDoHMessage(t, client, msg, nil)
+			requireResponse(t, msg, resp)
+		})
+	}
+}
+
+func TestHttpsProxyTrustedProxies(t *testing.T) {
 	// Prepare the proxy server.
 	tlsConf, caPem := createServerTLSConfig(t)
 	dnsProxy := createTestProxy(t, tlsConf)
@@ -29,30 +71,7 @@ func TestHttpsProxy(t *testing.T) {
 		return dnsProxy.Resolve(d)
 	}
 
-	roots := x509.NewCertPool()
-	ok := roots.AppendCertsFromPEM(caPem)
-	require.True(t, ok)
-
-	dialer := &net.Dialer{
-		Timeout: defaultTimeout,
-	}
-	dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
-		// Route request to the DNS-over-HTTPS server address.
-		return dialer.DialContext(ctx, network, dnsProxy.Addr(ProtoHTTPS).String())
-	}
-
-	transport := &http.Transport{
-		TLSClientConfig: &tls.Config{
-			ServerName: tlsServerName,
-			RootCAs:    roots,
-		},
-		DisableCompression: true,
-		DialContext:        dialContext,
-	}
-	client := http.Client{
-		Transport: transport,
-		Timeout:   defaultTimeout,
-	}
+	client := createTestHTTPClient(dnsProxy, caPem, false)
 
 	clientIP, proxyIP := net.IP{1, 2, 3, 4}, net.IP{127, 0, 0, 1}
 	msg := createTestMessage()
@@ -63,42 +82,14 @@ func TestHttpsProxy(t *testing.T) {
 		// Start listening.
 		serr := dnsProxy.Start()
 		require.NoError(t, serr)
-		t.Cleanup(func() {
-			derr := dnsProxy.Stop()
-			require.NoError(t, derr)
-		})
+		testutil.CleanupAndRequireSuccess(t, dnsProxy.Stop)
 
-		packed, err := msg.Pack()
-		require.NoError(t, err)
-
-		b := bytes.NewBuffer(packed)
-		req, err := http.NewRequest("POST", "https://test.com", b)
-		require.NoError(t, err)
-
-		req.Header.Set("Content-Type", "application/dns-message")
-		req.Header.Set("Accept", "application/dns-message")
-		// IP "1.2.3.4" will be used as a client address in DNSContext.
-		req.Header.Set("X-Forwarded-For", strings.Join(
-			[]string{clientIP.String(), proxyIP.String()},
-			",",
-		))
-
-		resp, err := client.Do(req)
-		require.NoError(t, err)
-
-		if resp != nil && resp.Body != nil {
-			t.Cleanup(func() {
-				resp.Body.Close()
-			})
+		hdrs := map[string]string{
+			"X-Forwarded-For": strings.Join([]string{clientIP.String(), proxyIP.String()}, ","),
 		}
 
-		body, err := io.ReadAll(resp.Body)
-		require.NoError(t, err)
-
-		reply := &dns.Msg{}
-		err = reply.Unpack(body)
-		require.NoError(t, err)
-		requireResponse(t, msg, reply)
+		resp := sendTestDoHMessage(t, client, msg, hdrs)
+		requireResponse(t, msg, resp)
 	}
 
 	t.Run("success", func(t *testing.T) {
@@ -300,7 +291,7 @@ func TestRemoteAddr(t *testing.T) {
 		}
 
 		t.Run(tc.name, func(t *testing.T) {
-			addr, prx, err := remoteAddr(r)
+			addr, prx, err := remoteAddr(r, false)
 			if tc.wantErr != "" {
 				assert.Equal(t, tc.wantErr, err.Error())
 
@@ -317,3 +308,90 @@ func TestRemoteAddr(t *testing.T) {
 		})
 	}
 }
+
+// sendTestDoHMessage sends the specified DNS message using client and returns
+// the DNS response.
+func sendTestDoHMessage(
+	t *testing.T,
+	client *http.Client,
+	m *dns.Msg,
+	hdrs map[string]string,
+) (resp *dns.Msg) {
+	packed, err := m.Pack()
+	require.NoError(t, err)
+
+	b := bytes.NewBuffer(packed)
+	u := fmt.Sprintf("https://%s/dns-query", tlsServerName)
+	req, err := http.NewRequest(http.MethodPost, u, b)
+	require.NoError(t, err)
+
+	req.Header.Set("Content-Type", "application/dns-message")
+	req.Header.Set("Accept", "application/dns-message")
+
+	for k, v := range hdrs {
+		req.Header.Set(k, v)
+	}
+
+	httpResp, err := client.Do(req)
+	require.NoError(t, err)
+	testutil.CleanupAndRequireSuccess(t, httpResp.Body.Close)
+
+	body, err := io.ReadAll(httpResp.Body)
+	require.NoError(t, err)
+
+	resp = &dns.Msg{}
+	err = resp.Unpack(body)
+	require.NoError(t, err)
+
+	return resp
+}
+
+// createTestHTTPClient creates an *http.Client that will be used to send
+// requests to the specified dnsProxy.
+func createTestHTTPClient(dnsProxy *Proxy, caPem []byte, http3Enabled bool) (client *http.Client) {
+	// prepare roots list so that the server cert was successfully validated.
+	roots := x509.NewCertPool()
+	roots.AppendCertsFromPEM(caPem)
+	tlsClientConfig := &tls.Config{
+		ServerName: tlsServerName,
+		RootCAs:    roots,
+	}
+
+	var transport http.RoundTripper
+
+	if http3Enabled {
+		transport = &http3.RoundTripper{
+			Dial: func(
+				ctx context.Context,
+				_ string,
+				tlsCfg *tls.Config,
+				cfg *quic.Config,
+			) (quic.EarlyConnection, error) {
+				addr := dnsProxy.Addr(ProtoHTTPS).String()
+				return quic.DialAddrEarlyContext(ctx, addr, tlsCfg, cfg)
+			},
+			TLSClientConfig:    tlsClientConfig,
+			QuicConfig:         &quic.Config{},
+			DisableCompression: true,
+		}
+	} else {
+		dialer := &net.Dialer{
+			Timeout: defaultTimeout,
+		}
+		dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
+			// Route request to the DNS-over-HTTPS server address.
+			return dialer.DialContext(ctx, network, dnsProxy.Addr(ProtoHTTPS).String())
+		}
+
+		transport = &http.Transport{
+			TLSClientConfig:    tlsClientConfig,
+			DisableCompression: true,
+			DialContext:        dialContext,
+		}
+	}
+
+	return &http.Client{
+		Transport: transport,
+		Timeout:   defaultTimeout,
+	}
+}
diff --git a/proxy/server_quic.go b/proxy/server_quic.go
index 59958f3e..ce8adc23 100644
--- a/proxy/server_quic.go
+++ b/proxy/server_quic.go
@@ -5,12 +5,14 @@ import (
 	"encoding/binary"
 	"errors"
 	"fmt"
-	"strings"
+	"io"
+	"math"
+	"net"
 	"time"
 
 	"github.com/AdguardTeam/dnsproxy/proxyutil"
 	"github.com/AdguardTeam/golibs/log"
-	"github.com/AdguardTeam/golibs/stringutil"
+	"github.com/bluele/gcache"
 	"github.com/lucas-clemente/quic-go"
 	"github.com/miekg/dns"
 )
@@ -31,6 +33,18 @@ var compatProtoDQ = []string{NextProtoDQ, "doq-i02", "doq-i00", "dq"}
 // better for clients written with ngtcp2.
 const maxQUICIdleTimeout = 5 * time.Minute
 
+// quicAddrValidatorCacheSize is the size of the cache that we use in the QUIC
+// address validator.  The value is chosen arbitrarily and we should consider
+// making it configurable.
+// TODO(ameshkov): make it configurable.
+const quicAddrValidatorCacheSize = 1000
+
+// quicAddrValidatorCacheTTL is time-to-live for cache items in the QUIC address
+// validator.  The value is chosen arbitrarily and we should consider making it
+// configurable.
+// TODO(ameshkov): make it configurable.
+const quicAddrValidatorCacheTTL = 30 * time.Minute
+
 const (
 	// DoQCodeNoError is used when the connection or stream needs to be closed,
 	// but there is no error to signal.
@@ -44,14 +58,19 @@ const (
 	DoQCodeProtocolError quic.ApplicationErrorCode = 2
 )
 
+// createQUICListeners creates QUIC listeners for the DoQ server.
 func (p *Proxy) createQUICListeners() error {
 	for _, a := range p.QUICListenAddr {
 		log.Info("Creating a QUIC listener")
 		tlsConfig := p.TLSConfig.Clone()
 		tlsConfig.NextProtos = compatProtoDQ
-		quicListen, err := quic.ListenAddr(a.String(), tlsConfig, &quic.Config{MaxIdleTimeout: maxQUICIdleTimeout})
+		quicListen, err := quic.ListenAddrEarly(
+			a.String(),
+			tlsConfig,
+			newServerQUICConfig(),
+		)
 		if err != nil {
-			return fmt.Errorf("starting quic listener: %w", err)
+			return fmt.Errorf("quic listener: %w", err)
 		}
 
 		p.quicListen = append(p.quicListen, quicListen)
@@ -63,13 +82,14 @@ func (p *Proxy) createQUICListeners() error {
 // quicPacketLoop listens for incoming QUIC packets.
 //
 // See also the comment on Proxy.requestGoroutinesSema.
-func (p *Proxy) quicPacketLoop(l quic.Listener, requestGoroutinesSema semaphore) {
+func (p *Proxy) quicPacketLoop(l quic.EarlyListener, requestGoroutinesSema semaphore) {
 	log.Info("Entering the DNS-over-QUIC listener loop on %s", l.Addr())
 	for {
 		conn, err := l.Accept(context.Background())
+
 		if err != nil {
 			if isQUICNonCrit(err) {
-				log.Tracef("quic connection closed or timeout: %s", err)
+				log.Tracef("quic connection closed or timed out: %s", err)
 			} else {
 				log.Error("reading from quic listen: %s", err)
 			}
@@ -140,7 +160,10 @@ func (p *Proxy) handleQUICStream(stream quic.Stream, conn quic.Connection) {
 	buf := *bufPtr
 	n, err := stream.Read(buf)
 
-	if n < minDNSPacketSize {
+	// Note that io.EOF does not really mean that there's any error, this is
+	// just a signal that there will be no data to read anymore from this
+	// stream.
+	if (err != nil && err != io.EOF) || n < minDNSPacketSize {
 		logShortQUICRead(err)
 
 		return
@@ -295,20 +318,42 @@ func logShortQUICRead(err error) {
 }
 
 // isQUICNonCrit returns true if err is a non-critical error, most probably
-// a timeout or a closed connection.
-//
-// TODO(a.garipov): Inspect and rewrite with modern error handling.
+// related to the current QUIC implementation.
+// TODO(ameshkov): re-test when updating quic-go.
 func isQUICNonCrit(err error) (ok bool) {
 	if err == nil {
 		return false
 	}
 
-	errStr := err.Error()
+	if errors.Is(err, quic.ErrServerClosed) {
+		// This error is returned when the QUIC listener was closed by us. This
+		// is an expected error, we don't need the detailed logs here.
+		return true
+	}
+
+	var qAppErr *quic.ApplicationError
+	if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {
+		// This error is returned when a QUIC connection was gracefully closed.
+		// No need to have detailed logs for it either.
+		return true
+	}
 
-	return strings.Contains(errStr, "server closed") ||
-		stringutil.ContainsFold(errStr, "no recent network activity") ||
-		strings.HasSuffix(errStr, "Application error 0x0") ||
-		errStr == "EOF"
+	if errors.Is(err, quic.Err0RTTRejected) {
+		// This error is returned on AcceptStream calls when the server rejects
+		// 0-RTT for some reason.  This is a common scenario, no need for extra
+		// logs.
+		return true
+	}
+
+	var qIdleErr *quic.IdleTimeoutError
+	if errors.As(err, &qIdleErr) {
+		// This error is returned when we're trying to accept a new stream from
+		// a connection that had no activity for over than the keep-alive
+		// timeout.  This is a common scenario, no need for extra logs.
+		return true
+	}
+
+	return false
 }
 
 // closeQUICConn quietly closes the QUIC connection.
@@ -318,3 +363,51 @@ func closeQUICConn(conn quic.Connection, code quic.ApplicationErrorCode) {
 		log.Debug("failed to close QUIC connection: %v", err)
 	}
 }
+
+// newServerQUICConfig creates *quic.Config populated with the default settings.
+// This function is supposed to be used for both DoQ and DoH3 server.
+func newServerQUICConfig() (conf *quic.Config) {
+	v := newQUICAddrValidator(quicAddrValidatorCacheSize, quicAddrValidatorCacheTTL)
+
+	return &quic.Config{
+		MaxIdleTimeout:           maxQUICIdleTimeout,
+		RequireAddressValidation: v.requiresValidation,
+		MaxIncomingStreams:       math.MaxUint16,
+		MaxIncomingUniStreams:    math.MaxUint16,
+	}
+}
+
+// quicAddrValidator is a helper struct that holds a small LRU cache of
+// addresses for which we do not require address validation.
+type quicAddrValidator struct {
+	cache gcache.Cache
+	ttl   time.Duration
+}
+
+// newQUICAddrValidator initializes a new instance of *quicAddrValidator.
+func newQUICAddrValidator(cacheSize int, ttl time.Duration) (v *quicAddrValidator) {
+	return &quicAddrValidator{
+		cache: gcache.New(cacheSize).LRU().Build(),
+		ttl:   ttl,
+	}
+}
+
+// requiresValidation determines if a QUIC Retry packet should be sent by the
+// client. This allows the server to verify the client's address but increases
+// the latency.
+func (v *quicAddrValidator) requiresValidation(addr net.Addr) (ok bool) {
+	key := addr.String()
+	if v.cache.Has(key) {
+		return false
+	}
+
+	err := v.cache.SetWithExpire(key, true, v.ttl)
+	if err != nil {
+		// Shouldn't happen, since we don't set a serialization function.
+		panic(fmt.Errorf("quic validator: setting cache item: %w", err))
+	}
+
+	// Address not found in the cache so return true to make sure the server
+	// will require address validation.
+	return true
+}
diff --git a/proxy/server_quic_test.go b/proxy/server_quic_test.go
index 3896dad0..5d93f84e 100644
--- a/proxy/server_quic_test.go
+++ b/proxy/server_quic_test.go
@@ -34,7 +34,7 @@ func TestQuicProxy(t *testing.T) {
 	addr := dnsProxy.Addr(ProtoQUIC)
 
 	// Open QUIC connection.
-	conn, err := quic.DialAddr(addr.String(), tlsConfig, nil)
+	conn, err := quic.DialAddrEarly(addr.String(), tlsConfig, nil)
 	require.NoError(t, err)
 	defer conn.CloseWithError(DoQCodeNoError, "")
 
diff --git a/upstream/bootstrap.go b/upstream/bootstrap.go
index a5e70edb..4568732d 100755
--- a/upstream/bootstrap.go
+++ b/upstream/bootstrap.go
@@ -306,3 +306,16 @@ func (n *bootstrapper) createDialContext(addresses []string) (dialContext dialHa
 		return nil, errors.List("all dialers failed", errs...)
 	}
 }
+
+// newContext creates a new context with deadline if needed.  If no timeout is
+// set cancel would be a simple noop.
+func (n *bootstrapper) newContext() (ctx context.Context, cancel context.CancelFunc) {
+	ctx = context.Background()
+	cancel = func() {}
+
+	if n.options.Timeout > 0 {
+		ctx, cancel = context.WithDeadline(ctx, time.Now().Add(n.options.Timeout))
+	}
+
+	return ctx, cancel
+}
diff --git a/upstream/upstream_doh.go b/upstream/upstream_doh.go
index 78a93afa..b8868aa6 100644
--- a/upstream/upstream_doh.go
+++ b/upstream/upstream_doh.go
@@ -9,7 +9,6 @@ import (
 	"net"
 	"net/http"
 	"net/url"
-	"os"
 	"sync"
 	"time"
 
@@ -74,12 +73,7 @@ func newDoH(uu *url.URL, opts *Options) (u Upstream, err error) {
 
 		quicConfig: &quic.Config{
 			KeepAlivePeriod: QUICKeepAlivePeriod,
-			// You can read more on address validation here:
-			// https://datatracker.ietf.org/doc/html/rfc9000#section-8.1
-			// 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).
-			TokenStore: quic.NewLRUTokenStore(1, 10),
+			TokenStore:      newQUICTokenStore(),
 		},
 	}, nil
 }
@@ -88,17 +82,69 @@ func newDoH(uu *url.URL, opts *Options) (u Upstream, err error) {
 func (p *dnsOverHTTPS) Address() string { return p.boot.URL.String() }
 
 // Exchange implements the Upstream interface for *dnsOverHTTPS.
-func (p *dnsOverHTTPS) Exchange(m *dns.Msg) (*dns.Msg, error) {
+func (p *dnsOverHTTPS) Exchange(m *dns.Msg) (resp *dns.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
+	defer func() {
+		// Restore the original ID to not break compatibility with proxies.
+		m.Id = id
+		if resp != nil {
+			resp.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.
+	hasClient := p.hasClient()
+
+	// Make the first attempt to send the DNS query.
+	resp, err = p.exchangeHTTPS(m)
+
+	// Make up to 2 attempts to re-create the HTTP client and send the request
+	// 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; hasClient && p.shouldRetry(err) && i < 2; i++ {
+		log.Debug("re-creating the HTTP client and retrying due to %v", err)
+
+		p.clientGuard.Lock()
+		p.client = nil
+		// Re-create the token store to make sure we're not trying to use invalid
+		// tokens for 0-RTT.
+		p.quicConfig.TokenStore = newQUICTokenStore()
+		p.clientGuard.Unlock()
+
+		resp, err = p.exchangeHTTPS(m)
+	}
+
+	if err != nil {
+		// If the request failed anyway, make sure we don't use this client.
+		p.clientGuard.Lock()
+		p.client = nil
+		p.clientGuard.Unlock()
+	}
+
+	return resp, err
+}
+
+// exchangeHTTPS creates an HTTP client and sends the DNS query using it.
+func (p *dnsOverHTTPS) exchangeHTTPS(m *dns.Msg) (resp *dns.Msg, err error) {
 	client, err := p.getClient()
 	if err != nil {
 		return nil, fmt.Errorf("initializing http client: %w", err)
 	}
 
 	logBegin(p.Address(), m)
-	r, err := p.exchangeHTTPSClient(m, client)
+	resp, err = p.exchangeHTTPSClient(m, client)
 	logFinish(p.Address(), err)
 
-	return r, err
+	return resp, err
 }
 
 // exchangeHTTPSClient sends the DNS query to a DoH resolver using the specified
@@ -125,16 +171,6 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(m *dns.Msg, client *http.Client) (*dn
 		defer resp.Body.Close()
 	}
 	if err != nil {
-		if errors.Is(err, os.ErrDeadlineExceeded) {
-			// If this is a timeout error, trying to forcibly re-create the HTTP
-			// client instance.
-			//
-			// See https://github.com/AdguardTeam/AdGuardHome/issues/3217.
-			p.clientGuard.Lock()
-			p.client = nil
-			p.clientGuard.Unlock()
-		}
-
 		return nil, fmt.Errorf("requesting %s: %w", p.boot.URL, err)
 	}
 
@@ -160,6 +196,38 @@ func (p *dnsOverHTTPS) exchangeHTTPSClient(m *dns.Msg, client *http.Client) (*dn
 	return &response, err
 }
 
+// hasClient returns true if this connection already has an active HTTP client.
+func (p *dnsOverHTTPS) hasClient() (ok bool) {
+	p.clientGuard.Lock()
+	defer p.clientGuard.Unlock()
+
+	return p.client != nil
+}
+
+// shouldRetry checks what error we have received and returns true if we should
+// re-create the HTTP client and retry the request.
+func (p *dnsOverHTTPS) shouldRetry(err error) (ok bool) {
+	if err == nil {
+		return false
+	}
+
+	var netErr net.Error
+	if errors.As(err, &netErr) && netErr.Timeout() {
+		// If this is a timeout error, trying to forcibly re-create the HTTP
+		// 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
+	}
+
+	if isQUICRetryError(err) {
+		return true
+	}
+
+	return false
+}
+
 // 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, err error) {
@@ -266,7 +334,7 @@ func (p *dnsOverHTTPS) createTransport() (t http.RoundTripper, err error) {
 func (p *dnsOverHTTPS) createTransportH3(
 	tlsConfig *tls.Config,
 	dialContext dialHandler,
-) (roundTripper *http3.RoundTripper, err error) {
+) (roundTripper http.RoundTripper, err error) {
 	if !p.supportsH3() {
 		return nil, errors.Error("HTTP3 support is not enabled")
 	}
@@ -276,21 +344,25 @@ func (p *dnsOverHTTPS) createTransportH3(
 		return nil, err
 	}
 
-	return &http3.RoundTripper{
+	rt := &http3.RoundTripper{
 		Dial: func(
 			ctx context.Context,
+
 			// Ignore the address and always connect to the one that we got
 			// from the bootstrapper.
 			_ string,
 			tlsCfg *tls.Config,
 			cfg *quic.Config,
 		) (c quic.EarlyConnection, err error) {
-			return quic.DialAddrEarlyContext(ctx, addr, tlsCfg, cfg)
+			c, err = quic.DialAddrEarlyContext(ctx, addr, tlsCfg, cfg)
+			return c, err
 		},
 		DisableCompression: true,
 		TLSClientConfig:    tlsConfig,
 		QuicConfig:         p.quicConfig,
-	}, nil
+	}
+
+	return rt, nil
 }
 
 // probeH3 runs a test to check whether QUIC is faster than TLS for this
diff --git a/upstream/upstream_doh_test.go b/upstream/upstream_doh_test.go
index 16a9a09f..2e0075e3 100644
--- a/upstream/upstream_doh_test.go
+++ b/upstream/upstream_doh_test.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"net"
 	"net/http"
+	"strconv"
 	"testing"
 	"time"
 
@@ -105,15 +106,88 @@ func TestUpstreamDoH(t *testing.T) {
 	}
 }
 
+func TestUpstreamDoH_serverRestart(t *testing.T) {
+	testCases := []struct {
+		name         string
+		httpVersions []HTTPVersion
+	}{
+		{
+			name:         "http2",
+			httpVersions: []HTTPVersion{HTTPVersion11, HTTPVersion2},
+		},
+		{
+			name:         "http3",
+			httpVersions: []HTTPVersion{HTTPVersion3},
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			// Run the first server instance.
+			srv := startDoHServer(t, testDoHServerOptions{
+				http3Enabled: true,
+			})
+
+			// Create a DNS-over-HTTPS upstream.
+			address := fmt.Sprintf("https://%s/dns-query", srv.addr)
+			u, err := AddressToUpstream(
+				address,
+				&Options{
+					InsecureSkipVerify: true,
+					HTTPVersions:       tc.httpVersions,
+					Timeout:            time.Second,
+				},
+			)
+			require.NoError(t, err)
+
+			// Test that the upstream works properly.
+			checkUpstream(t, u, address)
+
+			// Now let's restart the server on the same address.
+			_, portStr, err := net.SplitHostPort(srv.addr)
+			require.NoError(t, err)
+			port, err := strconv.Atoi(portStr)
+
+			// Shutdown the first server.
+			srv.Shutdown()
+
+			// Start the new one on the same port.
+			srv = startDoHServer(t, testDoHServerOptions{
+				http3Enabled: true,
+				port:         port,
+			})
+
+			// Check that everything works after restart.
+			checkUpstream(t, u, address)
+
+			// Stop the server again.
+			srv.Shutdown()
+
+			// Now try to send a message and make sure that it returns an error.
+			_, err = u.Exchange(createTestMessage())
+			require.Error(t, err)
+
+			// Start the server one more time.
+			srv = startDoHServer(t, testDoHServerOptions{
+				http3Enabled: true,
+				port:         port,
+			})
+
+			// Check that everything works after the second restart.
+			checkUpstream(t, u, address)
+		})
+	}
+}
+
 // testDoHServerOptions allows customizing testDoHServer behavior.
 type testDoHServerOptions struct {
 	http3Enabled     bool
 	delayHandshakeH2 time.Duration
 	delayHandshakeH3 time.Duration
+	port             int
 }
 
-// testDoHServer is an instance of a test DNS-over-HTTPS server that we use
-// for tests.
+// testDoHServer is an instance of a test DNS-over-HTTPS server.
 type testDoHServer struct {
 	// addr is the address that this server listens to.
 	addr string
@@ -126,9 +200,12 @@ type testDoHServer struct {
 
 	// serverH3 is an HTTP/3 server.
 	serverH3 *http3.Server
+
+	// listenerH3 that's used to serve HTTP/3.
+	listenerH3 quic.EarlyListener
 }
 
-// Shutdown stops the DOH server.
+// Shutdown stops the DoH server.
 func (s *testDoHServer) Shutdown() {
 	if s.server != nil {
 		_ = s.server.Shutdown(context.Background())
@@ -136,6 +213,7 @@ func (s *testDoHServer) Shutdown() {
 
 	if s.serverH3 != nil {
 		_ = s.serverH3.Close()
+		_ = s.listenerH3.Close()
 	}
 }
 
@@ -156,7 +234,8 @@ func startDoHServer(
 	}
 
 	// Listen TCP first.
-	tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0")
+	listenAddr := fmt.Sprintf("127.0.0.1:%d", opts.port)
+	tcpAddr, err := net.ResolveTCPAddr("tcp", listenAddr)
 	require.NoError(t, err)
 
 	tcpListen, err := net.ListenTCP("tcp", tcpAddr)
@@ -179,6 +258,7 @@ func startDoHServer(
 	tcpAddr = tcpListen.Addr().(*net.TCPAddr)
 
 	var serverH3 *http3.Server
+	var listenerH3 quic.EarlyListener
 
 	if opts.http3Enabled {
 		tlsConfigH3 := tlsConfig.Clone()
@@ -191,9 +271,7 @@ func startDoHServer(
 		}
 
 		serverH3 = &http3.Server{
-			TLSConfig:  tlsConfig.Clone(),
-			QuicConfig: &quic.Config{},
-			Handler:    handler,
+			Handler: handler,
 		}
 
 		// Listen UDP for the H3 server. Reuse the same port as was used for the
@@ -201,17 +279,18 @@ func startDoHServer(
 		udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", tcpAddr.Port))
 		require.NoError(t, err)
 
-		udpListen, err := net.ListenUDP("udp", udpAddr)
+		listenerH3, err = quic.ListenAddrEarly(udpAddr.String(), tlsConfigH3, &quic.Config{})
 		require.NoError(t, err)
 
 		// Run the H3 server.
-		go serverH3.Serve(udpListen)
+		go serverH3.ServeListener(listenerH3)
 	}
 
 	return &testDoHServer{
-		tlsConfig: tlsConfig,
-		server:    server,
-		serverH3:  serverH3,
+		tlsConfig:  tlsConfig,
+		server:     server,
+		serverH3:   serverH3,
+		listenerH3: listenerH3,
 		// Save the address that the server listens to.
 		addr: tcpAddr.String(),
 	}
diff --git a/upstream/upstream_quic.go b/upstream/upstream_quic.go
index eb98fd17..f8942def 100644
--- a/upstream/upstream_quic.go
+++ b/upstream/upstream_quic.go
@@ -9,6 +9,7 @@ import (
 	"time"
 
 	"github.com/AdguardTeam/dnsproxy/proxyutil"
+	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/log"
 	"github.com/lucas-clemente/quic-go"
 	"github.com/miekg/dns"
@@ -22,9 +23,6 @@ const (
 	// an internal error and is incapable of pursuing the transaction or the
 	// connection.
 	QUICCodeInternalError = quic.ApplicationErrorCode(1)
-	// QUICCodeProtocolError signals that the DoQ implementation encountered
-	// a protocol error and is forcibly aborting the connection.
-	QUICCodeProtocolError = quic.ApplicationErrorCode(2)
 	// QUICKeepAlivePeriod is the value that we pass to *quic.Config and that
 	// controls the period with with keep-alive frames are being sent to the
 	// connection. We set it to 20s as it would be in the quic-go@v0.27.1 with
@@ -49,11 +47,12 @@ type dnsOverQUIC struct {
 	// conn is the current active QUIC connection.  It can be closed and
 	// re-opened when needed.
 	conn quic.Connection
+	// connGuard protects conn and quicConfig.
+	connGuard 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
-	// sync.RWMutex protects conn and bytesPool.
-	sync.RWMutex
+	bytesPool      *sync.Pool
+	bytesPoolGuard sync.Mutex
 }
 
 // type check
@@ -73,12 +72,7 @@ func newDoQ(uu *url.URL, opts *Options) (u Upstream, err error) {
 		boot: b,
 		quicConfig: &quic.Config{
 			KeepAlivePeriod: QUICKeepAlivePeriod,
-			// You can read more on address validation here:
-			// https://datatracker.ietf.org/doc/html/rfc9000#section-8.1
-			// 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).
-			TokenStore: quic.NewLRUTokenStore(1, 10),
+			TokenStore:      newQUICTokenStore(),
 		},
 	}, nil
 }
@@ -87,25 +81,59 @@ func newDoQ(uu *url.URL, opts *Options) (u Upstream, err error) {
 func (p *dnsOverQUIC) Address() string { return p.boot.URL.String() }
 
 // Exchange implements the Upstream interface for *dnsOverQUIC.
-func (p *dnsOverQUIC) Exchange(m *dns.Msg) (res *dns.Msg, err error) {
-	var conn quic.Connection
-	conn, err = p.getConnection(true)
-	if err != nil {
-		return nil, err
-	}
-
+func (p *dnsOverQUIC) Exchange(m *dns.Msg) (resp *dns.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
+		// Restore the original ID to not break compatibility with proxies.
 		m.Id = id
-		if res != nil {
-			res.Id = id
+		if resp != nil {
+			resp.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.
+	resp, err = p.exchangeQUIC(m)
+
+	// Make up to 2 attempts to re-open the QUIC connection and send the request
+	// 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
+	// connection was closed (due to inactivity for example) AND the server
+	// refuses to open a 0-RTT connection.
+	for i := 0; hasConnection && p.shouldRetry(err) && i < 2; i++ {
+		log.Debug("re-creating the QUIC connection and retrying due to %v", err)
+
+		// Close the active connection to make sure we'll try to re-connect.
+		p.closeConnWithError(QUICCodeNoError)
+
+		// Retry sending the request.
+		resp, err = p.exchangeQUIC(m)
+	}
+
+	if err != nil {
+		// If we're unable to exchange messages, make sure the connection is
+		// closed and signal about an internal error.
+		p.closeConnWithError(QUICCodeInternalError)
+	}
+
+	return resp, err
+}
+
+// exchangeQUIC attempts to open a QUIC connection, send the DNS message
+// through it and return the response it got from the server.
+func (p *dnsOverQUIC) exchangeQUIC(m *dns.Msg) (resp *dns.Msg, err error) {
+	var conn quic.Connection
+	conn, err = p.getConnection(true)
+	if err != nil {
+		return nil, err
+	}
+
 	var buf []byte
 	buf, err = m.Pack()
 	if err != nil {
@@ -115,36 +143,34 @@ func (p *dnsOverQUIC) Exchange(m *dns.Msg) (res *dns.Msg, err error) {
 	var stream quic.Stream
 	stream, err = p.openStream(conn)
 	if err != nil {
-		p.closeConnWithError(QUICCodeInternalError)
-		return nil, fmt.Errorf("open new stream to %s: %w", p.Address(), err)
+		return nil, err
 	}
 
 	_, err = stream.Write(proxyutil.AddPrefix(buf))
 	if err != nil {
-		p.closeConnWithError(QUICCodeInternalError)
 		return nil, fmt.Errorf("failed to write to a QUIC stream: %w", err)
 	}
 
 	// 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.
-	// stream.Close() -- closes the write-direction of the stream.
+	// be sent on that stream. Note, that stream.Close() closes the
+	// write-direction of the stream, but does not prevent reading from it.
 	_ = stream.Close()
 
-	res, err = p.readMsg(stream)
-	if err != nil {
-		// If a peer encounters such an error condition, it is considered a
-		// fatal error.  It SHOULD forcibly abort the connection using QUIC's
-		// CONNECTION_CLOSE mechanism and SHOULD use the DoQ error code
-		// DOQ_PROTOCOL_ERROR.
-		p.closeConnWithError(QUICCodeProtocolError)
-	}
-	return res, err
+	return p.readMsg(stream)
+}
+
+// shouldRetry checks what error we received and decides whether it is required
+// to re-open the connection and retry sending the request.
+func (p *dnsOverQUIC) shouldRetry(err error) (ok bool) {
+	return isQUICRetryError(err)
 }
 
 // getBytesPool returns (creates if needed) a pool we store byte buffers in.
 func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
-	p.Lock()
+	p.bytesPoolGuard.Lock()
+	defer p.bytesPoolGuard.Unlock()
+
 	if p.bytesPool == nil {
 		p.bytesPool = &sync.Pool{
 			New: func() interface{} {
@@ -154,7 +180,7 @@ func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
 			},
 		}
 	}
-	p.Unlock()
+
 	return p.bytesPool
 }
 
@@ -164,59 +190,57 @@ func (p *dnsOverQUIC) getBytesPool() (pool *sync.Pool) {
 // close the existing one if needed.
 func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) {
 	var conn quic.Connection
-	p.RLock()
+	p.connGuard.RLock()
 	conn = p.conn
 	if conn != nil && useCached {
-		p.RUnlock()
+		p.connGuard.RUnlock()
+
 		return conn, nil
 	}
 	if conn != nil {
 		// we're recreating the connection, let's create a new one.
 		_ = conn.CloseWithError(QUICCodeNoError, "")
 	}
-	p.RUnlock()
+	p.connGuard.RUnlock()
 
-	p.Lock()
-	defer p.Unlock()
+	p.connGuard.Lock()
+	defer p.connGuard.Unlock()
 
 	var err error
 	conn, err = p.openConnection()
 	if err != nil {
-		// This does not look too nice, but QUIC (or maybe quic-go) doesn't
-		// seem stable enough. Maybe retransmissions aren't fully implemented
-		// in quic-go? Anyways, the simple solution is to make a second try when
-		// it fails to open the QUIC connection.
-		conn, err = p.openConnection()
-		if err != nil {
-			return nil, err
-		}
+		return nil, err
 	}
 	p.conn = conn
+
 	return conn, nil
 }
 
+// hasConnection returns true if there's an active QUIC connection.
+func (p *dnsOverQUIC) hasConnection() (ok bool) {
+	p.connGuard.Lock()
+	defer p.connGuard.Unlock()
+
+	return p.conn != nil
+}
+
 // openStream opens a new QUIC stream for the specified connection.
 func (p *dnsOverQUIC) openStream(conn quic.Connection) (quic.Stream, error) {
-	ctx := context.Background()
-
-	if p.boot.options.Timeout > 0 {
-		deadline := time.Now().Add(p.boot.options.Timeout)
-		var cancel context.CancelFunc
-		ctx, cancel = context.WithDeadline(context.Background(), deadline)
-		defer cancel() // avoid resource leak
-	}
+	ctx, cancel := p.boot.newContext()
+	defer cancel()
 
 	stream, err := conn.OpenStreamSync(ctx)
 	if err == nil {
 		return stream, nil
 	}
 
-	// try to recreate the connection.
+	// We can get here if the old QUIC connection is not valid anymore.  We
+	// should try to re-create the connection again in this case.
 	newConn, err := p.getConnection(false)
 	if err != nil {
 		return nil, err
 	}
-	// open a new stream.
+	// Open a new stream.
 	return newConn.OpenStreamSync(ctx)
 }
 
@@ -244,7 +268,10 @@ func (p *dnsOverQUIC) openConnection() (conn quic.Connection, err error) {
 
 	addr := udpConn.RemoteAddr().String()
 
-	conn, err = quic.DialAddrEarlyContext(context.Background(), addr, tlsConfig, p.quicConfig)
+	ctx, cancel := p.boot.newContext()
+	defer cancel()
+
+	conn, err = quic.DialAddrEarlyContext(ctx, addr, tlsConfig, p.quicConfig)
 	if err != nil {
 		return nil, fmt.Errorf("opening quic connection to %s: %w", p.Address(), err)
 	}
@@ -256,8 +283,8 @@ func (p *dnsOverQUIC) openConnection() (conn quic.Connection, err error) {
 // new queries were processed in another connection. We can do that in the case
 // of a fatal error.
 func (p *dnsOverQUIC) closeConnWithError(code quic.ApplicationErrorCode) {
-	p.Lock()
-	defer p.Unlock()
+	p.connGuard.Lock()
+	defer p.connGuard.Unlock()
 
 	if p.conn == nil {
 		// Do nothing, there's no active conn anyways.
@@ -269,6 +296,10 @@ func (p *dnsOverQUIC) closeConnWithError(code quic.ApplicationErrorCode) {
 		log.Error("failed to close the conn: %v", err)
 	}
 	p.conn = nil
+
+	// Re-create the token store to make sure we're not trying to use invalid
+	// tokens for 0-RTT.
+	p.quicConfig.TokenStore = newQUICTokenStore()
 }
 
 // readMsg reads the incoming DNS message from the QUIC stream.
@@ -297,3 +328,51 @@ func (p *dnsOverQUIC) readMsg(stream quic.Stream) (m *dns.Msg, err error) {
 
 	return m, nil
 }
+
+// newQUICTokenStore creates a new quic.TokenStore that is necessary to have
+// in order to benefit from 0-RTT.
+func newQUICTokenStore() (s quic.TokenStore) {
+	// You can read more on address validation here:
+	// https://datatracker.ietf.org/doc/html/rfc9000#section-8.1
+	// 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
+// quic-go issues, see the comments inside this function.
+// TODO(ameshkov): re-test when updating quic-go.
+func isQUICRetryError(err error) (ok bool) {
+	var qAppErr *quic.ApplicationError
+	if errors.As(err, &qAppErr) && qAppErr.ErrorCode == 0 {
+		// This error is often returned when the server has been restarted,
+		// 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
+		// clean up dead connections (this one is specific to DoH3 upstream):
+		// https://github.com/lucas-clemente/quic-go/issues/765
+		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.
+		// Reproducing is rather simple, stop the server and wait for 30 seconds
+		// then try to send another request via the same upstream.
+		return true
+	}
+
+	if errors.Is(err, quic.Err0RTTRejected) {
+		// 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
+}
diff --git a/upstream/upstream_quic_test.go b/upstream/upstream_quic_test.go
index 0a2fe21f..047d19b2 100644
--- a/upstream/upstream_quic_test.go
+++ b/upstream/upstream_quic_test.go
@@ -1,20 +1,33 @@
 package upstream
 
 import (
+	"context"
 	"crypto/tls"
+	"encoding/binary"
+	"fmt"
+	"io"
+	"net"
+	"strconv"
 	"testing"
+	"time"
 
+	"github.com/AdguardTeam/dnsproxy/proxyutil"
+	"github.com/AdguardTeam/golibs/log"
 	"github.com/lucas-clemente/quic-go"
+	"github.com/miekg/dns"
 	"github.com/stretchr/testify/require"
 )
 
 func TestUpstreamDoQ(t *testing.T) {
-	// Create a DNS-over-QUIC upstream
-	address := "quic://dns.adguard.com"
+	srv := startDoQServer(t, 0)
+	t.Cleanup(srv.Shutdown)
+
+	address := fmt.Sprintf("quic://%s", srv.addr)
 	var lastState tls.ConnectionState
 	u, err := AddressToUpstream(
 		address,
 		&Options{
+			InsecureSkipVerify: true,
 			VerifyConnection: func(state tls.ConnectionState) error {
 				lastState = state
 				return nil
@@ -47,3 +60,150 @@ func TestUpstreamDoQ(t *testing.T) {
 	// Make sure that the session has been resumed.
 	require.True(t, lastState.DidResume)
 }
+
+func TestUpstreamDoQ_serverRestart(t *testing.T) {
+	// Run the first server instance.
+	srv := startDoQServer(t, 0)
+
+	// Create a DNS-over-QUIC upstream.
+	address := fmt.Sprintf("quic://%s", srv.addr)
+	u, err := AddressToUpstream(address, &Options{InsecureSkipVerify: true, Timeout: time.Second})
+	require.NoError(t, err)
+
+	// Test that the upstream works properly.
+	checkUpstream(t, u, address)
+
+	// Now let's restart the server on the same address.
+	_, portStr, err := net.SplitHostPort(srv.addr)
+	require.NoError(t, err)
+	port, err := strconv.Atoi(portStr)
+
+	// Shutdown the first server.
+	srv.Shutdown()
+
+	// Start the new one on the same port.
+	srv = startDoQServer(t, port)
+
+	// Check that everything works after restart.
+	checkUpstream(t, u, address)
+
+	// Stop the server again.
+	srv.Shutdown()
+
+	// Now try to send a message and make sure that it returns an error.
+	_, err = u.Exchange(createTestMessage())
+	require.Error(t, err)
+
+	// Start the server one more time.
+	srv = startDoQServer(t, port)
+
+	// Check that everything works after the second restart.
+	checkUpstream(t, u, address)
+}
+
+// testDoHServer is an instance of a test DNS-over-QUIC server.
+type testDoQServer struct {
+	// addr is the address that this server listens to.
+	addr string
+
+	// tlsConfig is the TLS configuration that is used for this server.
+	tlsConfig *tls.Config
+
+	// listener is the QUIC connections listener.
+	listener quic.EarlyListener
+}
+
+// Shutdown stops the test server.
+func (s *testDoQServer) Shutdown() {
+	_ = s.listener.Close()
+}
+
+// Serve serves DoQ requests.
+func (s *testDoQServer) Serve() {
+	for {
+		conn, err := s.listener.Accept(context.Background())
+		if err == quic.ErrServerClosed {
+			// Finish serving on ErrServerClosed error.
+			return
+		}
+
+		if err != nil {
+			log.Debug("error while accepting a new connection: %v", err)
+		}
+
+		go s.handleQUICConnection(conn)
+	}
+}
+
+// handleQUICConnection handles incoming QUIC connection.
+func (s *testDoQServer) handleQUICConnection(conn quic.EarlyConnection) {
+	for {
+		stream, err := conn.AcceptStream(context.Background())
+		if err != nil {
+			_ = conn.CloseWithError(QUICCodeNoError, "")
+
+			return
+		}
+
+		go func() {
+			qErr := s.handleQUICStream(stream)
+			if qErr != nil {
+				_ = conn.CloseWithError(QUICCodeNoError, "")
+			}
+		}()
+	}
+}
+
+// handleQUICStream handles new QUIC streams, reads DNS messages and responds to
+// them.
+func (s *testDoQServer) handleQUICStream(stream quic.Stream) (err error) {
+	defer stream.Close()
+
+	buf := make([]byte, dns.MaxMsgSize+2)
+	_, err = stream.Read(buf)
+	if err != nil && err != io.EOF {
+		return err
+	}
+
+	req := &dns.Msg{}
+	packetLen := binary.BigEndian.Uint16(buf[:2])
+	err = req.Unpack(buf[2 : packetLen+2])
+	if err != nil {
+		return err
+	}
+
+	resp := respondToTestMessage(req)
+
+	buf, err = resp.Pack()
+	if err != nil {
+		return err
+	}
+
+	buf = proxyutil.AddPrefix(buf)
+	_, err = stream.Write(buf)
+
+	return err
+}
+
+// startDoQServer starts a test DoQ server.
+func startDoQServer(t *testing.T, port int) (s *testDoQServer) {
+	tlsConfig := createServerTLSConfig(t, "127.0.0.1")
+	tlsConfig.NextProtos = []string{NextProtoDQ}
+
+	listen, err := quic.ListenAddrEarly(
+		fmt.Sprintf("127.0.0.1:%d", port),
+		tlsConfig,
+		&quic.Config{},
+	)
+	require.NoError(t, err)
+
+	s = &testDoQServer{
+		addr:      listen.Addr().String(),
+		tlsConfig: tlsConfig,
+		listener:  listen,
+	}
+
+	go s.Serve()
+
+	return s
+}
diff --git a/vendor/github.com/bluele/gcache/LICENSE b/vendor/github.com/bluele/gcache/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..d1e7b03e39696805995bfed09c45cc2f28e87086
GIT binary patch
literal 1077
zcmWH^NLBFl3{mjOOis-!PF2wG^$gME;&RR}s4U7%&nQvQNY+#^GB7k(@G8wy@XpLF
zElT9#3P>%=%`7g?%+FKEELO-!ElN$QR7fvM%qvMv(NRb%N=;SBPg6+FNGwWE)ln$P
zS4hmOR47O-D$dVS$WJOs%*@NoOIJu#NX{>)<jPM|D9OkyRw&L-D=AMbN>xbAOHoKH
zF3wNROe{%FQAo*8F3nBND@iN?*_W1?lUl5xQIe6WpcD+%p`;16BqcR5hbuEr0VD^L
zQYg<X$;dA)Q7B3+E-A`P2AQFwkeQd9Q<?&D157$6GdB}z14ujA@5Njt`3j}QsXAbH
z>L}#qr(~v8>L}!<g1uExnv|1SoS~zTk_j?8sk9_jN1+%b2o6dekU#YEixi4eb8@(n
z^9wRli$S3RG9KnSuo_VK6o3M>1R6xeAd&Kn{9II@WEOL!l@{e?7H6cUfK;U9D-`F0
zZOlqdE&+*vj84nX$;mGVc`Z3VFC`P?-(o8+E>JutCgqo<g8c&ao<d%JNoF!6xWN$s
zb_66=p;E;ei8(n6NvY5POHEP8%;N$H!Msxh@^EoUVqQsRVva&Vei7I{bZ_c$afNue
zDg^txg@ii>xhi-DD+C1jhk3fVx+o|)1}k_5E9oeNdxm)UhlVJGI|c<g`h`R)_`4}M
z`b8>ud-}QPD7Z!h1i1zWEBFU-dHM$Uc)GgiD0uog`-Hl9`nfAOg@!2j`G+X@c=~#V
zxVk8W_$z=cgc{}P8VoYb*EPu5!_hCq(aF=tGbB=n%gr;y4`imBe~^NsLV#mXh^KR?
zk7JNRKxj~af3T~9qo0d{pTD1{pIeZppS!ECt6zwof~TK?pTB}@n5$oiLa>LUj}OQy
zF2~Ri5C0&L`xKo010sVw-917SJp6rJT!VrYoLm)rJRO~UTou4p`9&%?`#5^~>L|E4
z`Z~IUTo<I^AL8K}#063Yah*cAhbu@3WS^s-f}?YYr@tS_8_xcIAwiDLAvy{n{y`xM
z{!p#qp24m<3XVaZ!Jq(f3-b5X;Q|GxzZ*!2r=Nnezn`Bg#1v3agItY>5&s}CKQ!1C
Zq#EoN1s7LGA5TB`U<FS<l<3pr0s#G}RyY6v

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/README.md b/vendor/github.com/bluele/gcache/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..b8f124b223e31e61269aa713ae33ae192086de49
GIT binary patch
literal 5285
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
CSVp!0

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/arc.go b/vendor/github.com/bluele/gcache/arc.go
new file mode 100644
index 0000000000000000000000000000000000000000..e2015e9113490d48b47f13c1e4530b8a68c513e0
GIT binary patch
literal 9165
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

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/cache.go b/vendor/github.com/bluele/gcache/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..e13e6f1cb491407a3c21f2f897bbcd0bdb395216
GIT binary patch
literal 5189
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
OrXvMQUS<wgEf)Zq;<j!8

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/clock.go b/vendor/github.com/bluele/gcache/clock.go
new file mode 100644
index 0000000000000000000000000000000000000000..3acc3f0db3ef704823a11884cf8ddde02e324435
GIT binary patch
literal 800
zcmXR&OwLYBPgO`yPE5{7<>JcBEyyn_QPAMxR4T5_OIG6IR4U2LO;zI3<l-u+EJ#&w
z&dE>CR>;gNNi9lCOiop(=Hm3rFW1mi04dW8$;?gVs)Z^IN=?jxC@(H4Dork_uI1uN
zE6qz*@JlU+%V}srjjZP4EJ`gYEy`1X%T?Ec%mQoCC`v|%Ybu~w335e=f|V`A5nxHU
zX(b>tz)p8d%ua>42gUhdAucXX$CR?fyyR4k6o@G<rA3J)nfZB|ApKxB_@$P^jnL3k
zfU`liL!GXcmYAKI3<{!ZE>2E;eT9(3?9@Dkw4(f6h4jpljM5~%<osOyto*#B{L-A9
zRDH18^8BJ~D}~bHRE0!^w9JZ<(xOy_l8nR>g_Qi%Vuif?5{0zVoV3iG90gGL>UkDN
zr55FDXmW9K=H-`LL7kgelB!{7X<?!R;X4)-W#;H8nCK`NAR%xhg@!n5>Tq#_0~Z`H
za7QaZA`0Z8y!>*AK_Dx+xHxl5OHwNoKxslRDBKssfrpDmTC#!`+zhOdla{OpHb5`P
zCqFqG<n)x(wA3O6gm`EkDAYhQ;D}61*2~K;M~y^qB%oV{E7m}MfSZoPQiPol>lAE3
R4$8|f*K<rs(MSQsJOEA+0iFN=

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/lfu.go b/vendor/github.com/bluele/gcache/lfu.go
new file mode 100644
index 0000000000000000000000000000000000000000..9a4e3dfeb6b449233f04377dc3d403c5238d60d9
GIT binary patch
literal 8171
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

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/lru.go b/vendor/github.com/bluele/gcache/lru.go
new file mode 100644
index 0000000000000000000000000000000000000000..a85d6603981a92e2e60e7d81a5dc1f5a3b410e25
GIT binary patch
literal 6631
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

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/simple.go b/vendor/github.com/bluele/gcache/simple.go
new file mode 100644
index 0000000000000000000000000000000000000000..7310af141c1ecd25e8b2befd5c8f3e60bce31328
GIT binary patch
literal 6402
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
D-%$oW

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/singleflight.go b/vendor/github.com/bluele/gcache/singleflight.go
new file mode 100644
index 0000000000000000000000000000000000000000..2c6285e826e0d9a66bc71a61bfd9532f8500fc72
GIT binary patch
literal 2117
zcmXR&OwLYBPgO`yPE5{7<>J!U;&RR}s4U7%&nQtaGB7k!aL><A&q-DA%uCke;_}H%
zPR%P$O;IS#OGzzKD9K1wa4Z1nRDjCpD1@aJ6=&w>DH!P)C}@BbC_$x^G_AQR^Gg+S
z6Dt++@=Fv-i&GU!GBS%5(lT>W6;dmbQwvHIGV>IY^K%PwG86NXQx(cHOESQgK#kDj
zii8@FpHz~VnWvDbkepvosgR$BtV$uVgo{f71TsoW3as??%gf9462Z>Y%P&gT&w+TN
zSl`Ff+0`%DRo6(*fQu_MFDJFQSfMDjur#wMHANw*QX#RRASW|9F)1fiAt$k1A-_l=
zF})}?HASH$ALP*TqRf)aymTFf;{3Fd^2DN4u9VE;lA_F{(h`&qf;wIyvseLHL4KY>
zVxEGMW3Yl}u#$q4W3Xqi4p+Emh=+e@h(fqyP>`cvh^K3?f`5>Lv%jB<XNaf2U$BC|
zn}VZXq=L7npNoz{YGz4BYLP-}ML`kB?fFFtnV_&sP0`~DP6dT-DlA?U(()nERa}so
zoSBxHtdNtKmtG2v`uwugqP)z!bcKS{qTI}4Q0x^eB<7`X<z(h&mL!&B=I0e7ML5(u
zdR$uiTwMD43L&64%*{_J%}G@#D9SI(Oi3+PNK{BE1qWV9szO?6UNXoKh2+GX9EIZ2
z0<dc{^YcKub5oNu67w>PbM?5mGII;^i%JxfiYxPymB40$b!38EqmY@Wo0bDkmia}H
zG?EGmW|w@hv0NpU1*u?`LUBn^X>y4|H5X@jx`G18EWPl=%o6va{L%t0&a%WDh0MH?
z)S|S+<kad~F3!}VB8AkVqWmJRTCkO1bqYnPAV;R=m4LjGoRe5w3`%6>`9;~FkO75e
zE=VRXF*miiATc==oE|bVlQTemD$UC*L8wno%u`4LCGgZ_aKZp5YDDNDg@+!*7Z4X9
zyaCRd3eKQx#>JUi3QE{uU-_1nq*j1<Fs!c+ihz>T<dR~ATrSRBuy}4_K{S$wW3|9x
zs;{q*lUS9RQ>l=dmsygTn3GwRngR_0mwbiP3b12A0iU8!lv+|+lvfPSVnwONr8yAK
zf%0~GW?5<;Jc4x;auc&N^U^_KP+VFBN`Q$a3i)|Cl?wTJun+*14VlGA(FsbKi3$*t
zvQsM+5=#^kK_--B=BDZ?c&4GGrR4nFRFGyJuxk*Z2=Y{FkwSSQDC9vVmSm)Y49PFb
zOwY_q%mHOtaKVyVk_wK&qSWNn%(7I7uZk0MQx%|I)8hiAR|So91ubwyXezkmYk=H^
z6o@(sX%Js)Xewx+h=Ws-rj9~pF(}O`B<1Jlpeq51=|Ga4CMcDr>*bc}`Q#^OYiM$D
zmVspytZWt1^}zA2m!4VzvR+4_q^LAilZ!Jm4Ww7W)>a`eGY6!f6Kp_eUJld<&LVJp
zDU|6bq$TDQr|N(da&gvzwWjOkqG?sI1;tvb2G(?<3DcLXqmZ8saaL}0c4}p;HHcr$
z#ff2iW}1Q`)b}81m}~MfbKvgrPObFIFLBE+%}a3w=R8i3>o}A3%G1GF6%-IKeaU)d
zi3pv^dZ|T4U^gX$9Fvz?t^ta9O)iLL$CMNeLrpG_o4}4#uvJI~^Gfv);e+PD^n7qo
z=j3Q4>nLQWR)XSIlZz8(KYn-D!ea~)Vd$nHq5>3q;J5{4GV~M%@`FaQ0yt}e9f4d#
zfgA+RVmJ~TdisJSK~Tzoh8;N26l@jJ@<0I#4rQ18yi^TME@;@m5?@McPHIW2M!H@u
U*b$I`L<%}CSfC&q%vH+;04ox;y#N3J

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/stats.go b/vendor/github.com/bluele/gcache/stats.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca0bf3185ae7e1da68114d2ba4cc3543cbbadf4f
GIT binary patch
literal 1003
zcmXR&OwLYBPgO`yPE5{7<>JcBEyyn_QPAMxR4T5_OV&>;$<NJ9R^rm+;wq^uNL45<
zNh~RLOioTMF3vAf$jmEAElNvFPF1Mp;`GQYan3KzE78zYD9y|(F*D)f^vx_TMi=wR
z&(AI`Ko|AMED1_1N!8F)NXyAj1Zl42;?ma#+m~5fl9^nLWWPdjNl|HX3CPk6sHF;U
zJ9A-n!bBhjW#%OprRJvQl_+FnmMA2H9LkkenwPAgQCy;+1@@Mvf@fZG5r#KF))b|d
zlosVFKte;$F(oAw>_ZK;;u1ZW6Lb^|HM!6&1GxsDZI~fM$U2aT;Lt;|59VeCh&zk%
z`4OfT9!;1Q`s62K^B~N3a5uxP1Gx#8FX5_*w-96;*o!b5k<tv@&Kz($!euLx4s7-o
zm*^pdgSJ9(i5`+Wp|J=}lW-f05=&AQ((;QGz<C7}#wD3~>6obqst=KtK_Q%xtfP>d
ztYBpevI}9i4v33vHD^hFNn#F2T}HBkwnA<)7iVUg0z}Z(R>1&d7AGW{6%6zYxHxOM
XU|eX<(a1>FRM3azBMq=tP(T6zT?0x^

literal 0
HcmV?d00001

diff --git a/vendor/github.com/bluele/gcache/utils.go b/vendor/github.com/bluele/gcache/utils.go
new file mode 100644
index 0000000000000000000000000000000000000000..1f784e4c4cf3a5e8715c79ce817b5567934c2200
GIT binary patch
literal 149
zcmXR&OwLYBPgO`yPE5{7<>E>!%}Z9u&CK)6E77RXQK(eN%q!6Z5en5@oSA6~6$&;A
jl^`}}QEEwPQJz8t7iTROj8n-~3o|0I0=E%%1dIRxt=KJe

literal 0
HcmV?d00001

diff --git a/vendor/modules.txt b/vendor/modules.txt
index 170d03040e5282e5a3f9ca4ddfd1e9471e212b05..9671387919c21adc6bffcb9e9c3d577e26398819 100644
GIT binary patch
delta 54
zcmdmD++wo9npq?%r!+MuRX;sBF*zeuq0B(fK+kCMLJ^V4|5$i=kR><kFn5Rn0RLqY
AQ2+n{

delta 12
TcmZoM*<!rGnt5|KbEOynA2tMD

-- 
GitLab