From 823fa92f99c4bf1093bc922d4f47ff2f94314781 Mon Sep 17 00:00:00 2001 From: Andrey Meshkov <am@adguard.com> Date: Fri, 16 Sep 2022 18:39:28 +0300 Subject: [PATCH] Pull request: Added DoH3 support, added TLS resumption Merge in DNS/dnsproxy from doh3 to master Squashed commit of the following: commit 93dc50875caf2df86ce08f22f5fb74e33b7b5ac0 Author: Andrey Meshkov <am@adguard.com> Date: Fri Sep 16 18:33:38 2022 +0300 fix review comments commit d19fd61eb69f31c94a9374396cbbefeb566a2163 Author: Andrey Meshkov <am@adguard.com> Date: Fri Sep 16 17:28:21 2022 +0300 upstream: added comments, minor fixes commit 9e4bf71275e9d1d3bc1cd72e27812548e8158402 Author: Andrey Meshkov <am@adguard.com> Date: Fri Sep 16 16:59:57 2022 +0300 upstream: added DoH3 support, added TLS resumption The changes are pretty considerable in this PR. First of all, DoH3 support has been added to dnsOverHTTPS. I haven't added a new type of upstream for that, but added it to the already existing one. Configuring supported HTTP versions is possible via upstream.Options. When all versions are enabled, it will "probe" both TLS and QUIC and choose the one that was faster (just like it's done in Chrome). Command-line interface now supports a new argument "http3" that is supposed to enable HTTP/3 globally. At this point it will only enable it for upstreams, but in the future it will also enable it for the DoH server. One more important change here is the introduction of TLS sessions cache. It appears that we weren't benefiting from TLS session resumption mechanism at all, thank god this is finally fixed. Finally, AddressToUpstream now supports "h3://" scheme for those who want to try DoH3 for a particular upstream without enabling it globally. The reasoning for implementing this custom scheme is the following: currently, only a small share of public resolvers fully support DoH3. Users may not want to spend time "probing" every upstream for H3. commit 8c76e435860699a2d5815fc702b7a7e928eba3ed Merge: 1145771 1dd831b Author: Andrey Meshkov <am@adguard.com> Date: Fri Sep 16 12:05:34 2022 +0300 Merge branch 'justus-forks-doq-0rtt' into doh3 commit 1145771f7621be5778cf14b47ccfb4aa20d07c81 Author: Andrey Meshkov <am@adguard.com> Date: Fri Sep 16 11:45:42 2022 +0300 upstream: initial attempt to add a DOH3 upstream --- .github/workflows/lint.yaml | 2 +- README.md | 30 +- go.mod | 1 + go.sum | 6 + main.go | 16 ++ proxy/errors.go | 10 +- proxy/server_https.go | 18 +- proxy/upstreams.go | 8 +- proxyutil/udp_unix.go | 5 +- upstream/bootstrap.go | 155 ++++++---- upstream/upstream.go | 79 ++++-- upstream/upstream_dnscrypt.go | 7 +- upstream/upstream_doh.go | 243 +++++++++++++++- upstream/upstream_doh_test.go | 264 ++++++++++++++++++ upstream/upstream_dot.go | 7 +- upstream/upstream_plain.go | 8 +- upstream/upstream_pool.go | 57 ++-- upstream/upstream_pool_test.go | 33 ++- upstream/upstream_quic.go | 80 +++--- upstream/upstream_quic_test.go | 23 +- upstream/upstream_test.go | 183 +++++++++--- .../lucas-clemente/quic-go/http3/body.go | Bin 0 -> 2970 bytes .../lucas-clemente/quic-go/http3/client.go | Bin 0 -> 12686 bytes .../quic-go/http3/error_codes.go | Bin 0 -> 2089 bytes .../lucas-clemente/quic-go/http3/frames.go | Bin 0 -> 3740 bytes .../quic-go/http3/gzip_reader.go | Bin 0 -> 798 bytes .../quic-go/http3/http_stream.go | Bin 0 -> 1706 bytes .../lucas-clemente/quic-go/http3/request.go | Bin 0 -> 2599 bytes .../quic-go/http3/request_writer.go | Bin 0 -> 8116 bytes .../quic-go/http3/response_writer.go | Bin 0 -> 2712 bytes .../lucas-clemente/quic-go/http3/roundtrip.go | Bin 0 -> 7166 bytes .../lucas-clemente/quic-go/http3/server.go | Bin 0 -> 22266 bytes .../marten-seemann/qpack/.codecov.yml | Bin 0 -> 103 bytes .../marten-seemann/qpack/.gitignore | Bin 0 -> 110 bytes .../marten-seemann/qpack/.gitmodules | Bin 0 -> 126 bytes .../marten-seemann/qpack/.golangci.yml | Bin 0 -> 447 bytes .../marten-seemann/qpack/LICENSE.md | Bin 0 -> 1054 bytes .../github.com/marten-seemann/qpack/README.md | Bin 0 -> 1203 bytes .../marten-seemann/qpack/decoder.go | Bin 0 -> 6190 bytes .../marten-seemann/qpack/encoder.go | Bin 0 -> 2616 bytes .../marten-seemann/qpack/header_field.go | Bin 0 -> 477 bytes .../marten-seemann/qpack/static_table.go | Bin 0 -> 9113 bytes .../github.com/marten-seemann/qpack/varint.go | Bin 0 -> 1589 bytes vendor/modules.txt | Bin 6446 -> 6580 bytes 44 files changed, 999 insertions(+), 236 deletions(-) create mode 100644 upstream/upstream_doh_test.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/body.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/client.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/frames.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/http_stream.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/request.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go create mode 100644 vendor/github.com/lucas-clemente/quic-go/http3/server.go create mode 100644 vendor/github.com/marten-seemann/qpack/.codecov.yml create mode 100644 vendor/github.com/marten-seemann/qpack/.gitignore create mode 100644 vendor/github.com/marten-seemann/qpack/.gitmodules create mode 100644 vendor/github.com/marten-seemann/qpack/.golangci.yml create mode 100644 vendor/github.com/marten-seemann/qpack/LICENSE.md create mode 100644 vendor/github.com/marten-seemann/qpack/README.md create mode 100644 vendor/github.com/marten-seemann/qpack/decoder.go create mode 100644 vendor/github.com/marten-seemann/qpack/encoder.go create mode 100644 vendor/github.com/marten-seemann/qpack/header_field.go create mode 100644 vendor/github.com/marten-seemann/qpack/static_table.go create mode 100644 vendor/github.com/marten-seemann/qpack/varint.go diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index ccc01397..12ae895a 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -29,7 +29,7 @@ jobs: with: # This field is required. Dont set the patch version to always use # the latest patch version. - version: v1.45.1 + version: v1.48.0 notify: needs: - golangci diff --git a/README.md b/README.md index 50c963ee..ff36c26c 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ Usage: dnsproxy [OPTIONS] Application Options: - --config-path= yaml configuration file. Minimal working configuration in config.yaml.dist. Options passed - through command line will override the ones from this file. + --config-path= yaml configuration file. Minimal working configuration in config.yaml.dist. + Options passed through command line will override the ones from this file. -v, --verbose Verbose output (optional) -o, --output= Path to the log file. If not set, write to stdout. -l, --listen= Listening addresses @@ -52,10 +52,10 @@ Application Options: --tls-min-version= Minimum TLS version, for example 1.0 --tls-max-version= Maximum TLS version, for example 1.3 --insecure Disable secure TLS certificate validation - -g, --dnscrypt-config= Path to a file with DNSCrypt configuration. You can generate one using - https://github.com/ameshkov/dnscrypt - -u, --upstream= An upstream to be used (can be specified multiple times). You can also specify path to a file - with the list of servers + -g, --dnscrypt-config= Path to a file with DNSCrypt configuration. You can generate one using https://github.com/ameshkov/dnscrypt + --http3 Enable HTTP/3 support + -u, --upstream= An upstream to be used (can be specified multiple times). + You can also specify path to a file with the list of servers -b, --bootstrap= Bootstrap DNS for DoH and DoT, can be specified multiple times (default: 8.8.8.8:53) -f, --fallback= Fallback resolvers to use when regular ones are unavailable, can be specified multiple times. You can also specify path to a file with the list of servers @@ -63,8 +63,8 @@ Application Options: --fastest-addr Respond to A or AAAA requests only with the fastest IP address --cache If specified, DNS cache is enabled --cache-size= Cache size (in bytes). Default: 64k - --cache-min-ttl= Minimum TTL value for DNS entries, in seconds. Capped at 3600. Artificially extending TTLs - should only be done with careful consideration. + --cache-min-ttl= Minimum TTL value for DNS entries, in seconds. Capped at 3600. + Artificially extending TTLs should only be done with careful consideration. --cache-max-ttl= Maximum TTL value for DNS entries, in seconds. --cache-optimistic If specified, optimistic DNS cache is enabled -r, --ratelimit= Ratelimit (requests per second) @@ -75,8 +75,8 @@ Application Options: --dns64-prefix= If specified, this is the DNS64 prefix dnsproxy will be using when it works as a DNS64 server. If not specified, dnsproxy uses the 'Well-Known Prefix' 64:ff9b:: --ipv6-disabled If specified, all AAAA requests will be replied with NoError RCode and empty answer - --bogus-nxdomain= Transform the responses containing at least a single IP that matches specified addresses and - CIDRs into NXDOMAIN. Can be specified multiple times. + --bogus-nxdomain= Transform the responses containing at least a single IP that matches specified addresses + and CIDRs into NXDOMAIN. Can be specified multiple times. --udp-buf-size= Set the size of the UDP buffer in bytes. A value <= 0 will use the system default. --max-go-routines= Set the maximum number of go routines. A value <= 0 will not not set a maximum. --version Prints the program version @@ -144,6 +144,16 @@ DNS-over-QUIC upstream: ./dnsproxy -u quic://dns.adguard.com ``` +DNS-over-HTTPS upstream with enabled HTTP/3 support (chooses it if it's faster): +```shell +./dnsproxy -u https://dns.google/dns-query --http3 +``` + +DNS-over-HTTPS upstream with forced HTTP/3 (no fallback to other protocol): +```shell +./dnsproxy -u h3://dns.google/dns-query +``` + DNSCrypt upstream ([DNS Stamp](https://dnscrypt.info/stamps) of AdGuard DNS): ```shell ./dnsproxy -u sdns://AQIAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20 diff --git a/go.mod b/go.mod index a011f4cb..52232f05 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/golang/mock v1.6.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.2 // indirect github.com/marten-seemann/qtls-go1-19 v0.1.0 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect diff --git a/go.sum b/go.sum index be7c9b39..484a8304 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucas-clemente/quic-go v0.29.0 h1:Vw0mGTfmWqGzh4jx/kMymsIkFK6rErFVmg+t9RLrnZE= github.com/lucas-clemente/quic-go v0.29.0/go.mod h1:CTcNfLYJS2UuRNB+zcNlgvkjBhxX6Hm3WUxxAQx2mgE= +github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= +github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qtls-go1-18 v0.1.2 h1:JH6jmzbduz0ITVQ7ShevK10Av5+jBEKAHMntXmIV7kM= github.com/marten-seemann/qtls-go1-18 v0.1.2/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= github.com/marten-seemann/qtls-go1-19 v0.1.0 h1:rLFKD/9mp/uq1SYGYuVZhm83wkmU95pK5df3GufyYYU= @@ -55,6 +57,7 @@ github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= @@ -87,6 +90,7 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -104,6 +108,7 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -117,6 +122,7 @@ golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVq golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= diff --git a/main.go b/main.go index ecc3bdd4..df92091f 100644 --- a/main.go +++ b/main.go @@ -80,6 +80,11 @@ type Options struct { // Path to the DNSCrypt configuration file 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. + HTTP3 bool `yaml:"http3" long:"http3" description:"Enable HTTP/3 support" optional:"yes" optional-value:"false"` + // Upstream DNS servers settings // -- @@ -292,7 +297,18 @@ func createProxyConfig(options *Options) proxy.Config { func initUpstreams(config *proxy.Config, options *Options) { // Init upstreams upstreams := loadServersList(options.Upstreams) + + httpVersions := upstream.DefaultHTTPVersions + if options.HTTP3 { + httpVersions = []upstream.HTTPVersion{ + upstream.HTTPVersion3, + upstream.HTTPVersion2, + upstream.HTTPVersion11, + } + } + upsOpts := &upstream.Options{ + HTTPVersions: httpVersions, InsecureSkipVerify: options.Insecure, Bootstrap: options.BootstrapDNS, Timeout: defaultTimeout, diff --git a/proxy/errors.go b/proxy/errors.go index 88089c92..241fec6b 100644 --- a/proxy/errors.go +++ b/proxy/errors.go @@ -12,11 +12,11 @@ import ( // isEPIPE checks if the underlying error is EPIPE. syscall.EPIPE exists on all // OSes except for Plan 9. Validate with: // -// $ for os in $(go tool dist list | cut -d / -f 1 | sort -u) -// do -// echo -n "$os" -// env GOOS="$os" go doc syscall.EPIPE | grep -F -e EPIPE -// done +// $ for os in $(go tool dist list | cut -d / -f 1 | sort -u) +// do +// echo -n "$os" +// env GOOS="$os" go doc syscall.EPIPE | grep -F -e EPIPE +// done // // For the Plan 9 version see ./errors_plan9.go. func isEPIPE(err error) (ok bool) { diff --git a/proxy/server_https.go b/proxy/server_https.go index 9fc15536..7324fc14 100644 --- a/proxy/server_https.go +++ b/proxy/server_https.go @@ -56,11 +56,10 @@ func (p *Proxy) listenHTTPS(srv *http.Server, l net.Listener) { // ServeHTTP is the http.RequestHandler 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. -// +// - 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) { log.Tracef("Incoming HTTPS request on %s", r.URL) @@ -159,11 +158,10 @@ func (p *Proxy) respondHTTPS(d *DNSContext) error { // suitable r's header. It returns nil if r doesn't contain any information // about real client's IP address. Current headers priority is: // -// 1. CF-Connecting-IP -// 2. True-Client-IP -// 3. X-Real-IP -// 4. X-Forwarded-For -// +// 1. CF-Connecting-IP +// 2. True-Client-IP +// 3. X-Real-IP +// 4. X-Forwarded-For func realIPFromHdrs(r *http.Request) (realIP net.IP) { for _, h := range []string{ // Headers set by CloudFlare proxy servers. diff --git a/proxy/upstreams.go b/proxy/upstreams.go index a57b240f..f85255a5 100644 --- a/proxy/upstreams.go +++ b/proxy/upstreams.go @@ -72,13 +72,7 @@ func ParseUpstreamsConfig(upstreamConfig []string, options *upstream.Options) (* dnsUpstream, ok := upstreamsIndex[u] if !ok { // create an upstream - dnsUpstream, err = upstream.AddressToUpstream( - u, - &upstream.Options{ - Bootstrap: options.Bootstrap, - Timeout: options.Timeout, - InsecureSkipVerify: options.InsecureSkipVerify, - }) + dnsUpstream, err = upstream.AddressToUpstream(u, options.Clone()) if err != nil { err = fmt.Errorf("cannot prepare the upstream %s (%s): %s", l, options.Bootstrap, err) diff --git a/proxyutil/udp_unix.go b/proxyutil/udp_unix.go index b5e625c3..e9d13dab 100644 --- a/proxyutil/udp_unix.go +++ b/proxyutil/udp_unix.go @@ -15,9 +15,8 @@ import ( // connection to receive an appropriate OOB data. For both versions the flags // are: // -// FlagDst -// FlagInterface -// +// - FlagDst +// - FlagInterface const ( ipv4Flags ipv4.ControlFlags = ipv4.FlagDst | ipv4.FlagInterface ipv6Flags ipv6.ControlFlags = ipv6.FlagDst | ipv6.FlagInterface diff --git a/upstream/bootstrap.go b/upstream/bootstrap.go index a78d6939..a5e70edb 100755 --- a/upstream/bootstrap.go +++ b/upstream/bootstrap.go @@ -12,53 +12,66 @@ import ( "github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/log" - "golang.org/x/net/http2" ) -// NextProtoDQ is the ALPN token for DoQ. During connection establishment, -// DNS/QUIC support is indicated by selecting the ALPN token "dq" in the +// NextProtoDQ is the ALPN token for DoQ. During the connection establishment, +// DNS/QUIC support is indicated by selecting the ALPN token "doq" in the // crypto handshake. -// Current draft version: -// https://datatracker.ietf.org/doc/html/draft-ietf-dprive-dnsoquic-02 -const NextProtoDQ = "doq-i02" +// The current draft version is https://datatracker.ietf.org/doc/rfc9250/. +const NextProtoDQ = "doq" // compatProtoDQ is a list of ALPN tokens used by a QUIC connection. // NextProtoDQ is the latest draft version supported by dnsproxy, but it also // includes previous drafts. -var compatProtoDQ = []string{NextProtoDQ, "doq-i00", "dq", "doq"} +var compatProtoDQ = []string{NextProtoDQ, "doq-i00", "dq", "doq-i02"} -// RootCAs is the CertPool that must be used by all upstreams -// Redefining RootCAs makes sense on iOS to overcome the 15MB memory limit of the NEPacketTunnelProvider -// nolint +// RootCAs is the CertPool that must be used by all upstreams. Redefining +// RootCAs makes sense on iOS to overcome the 15MB memory limit of the +// NEPacketTunnelProvider. var RootCAs *x509.CertPool -// CipherSuites - custom list of TLSv1.2 ciphers -// nolint +// CipherSuites is a custom list of TLSv1.2 ciphers. var CipherSuites []uint16 -// TODO: refactor bootstrapper, it's overcomplicated and hard to understand what it does +// TODO(ameshkov): refactor bootstrapper, it's overcomplicated and hard to +// understand what it does. type bootstrapper struct { - URL *url.URL - resolvers []*Resolver // list of Resolvers to use to resolve hostname, if necessary - dialContext dialHandler // specifies the dial function for creating unencrypted TCP connections. + // URL is the upstream server address. + URL *url.URL + + // resolvers is a list of *net.Resolver to use to resolve the upstream + // hostname, if necessary. + resolvers []*Resolver + + // dialContext is the dial function for creating unencrypted TCP + // connections. + dialContext dialHandler + + // resolvedConfig is a *tls.Config that is used for encrypted DNS protocols. resolvedConfig *tls.Config - sync.RWMutex - // stores options for AddressToUpstream func: - // callbacks for checking certificates, timeout, - // the need to verify the server certificate, - // the addresses of upstream servers, etc + // sessionsCache is necessary to achieve TLS session resumption. We create + // once when the bootstrapper is created and re-use every time when we need + // to create a new tls.Config. + sessionsCache tls.ClientSessionCache + + // guard protects dialContext and resolvedConfig. + guard sync.RWMutex + + // options is the Options that were passed to the AddressToUpstream + // function. It configures different upstream properties: callbacks for + // checking certificates, timeout, etc. options *Options } -// newBootstrapperResolved creates a new bootstrapper that already contains resolved config. -// This can be done only in the case when we already know the resolver IP address. -// options -- Upstream customization options +// newBootstrapperResolved creates a new bootstrapper that already contains +// resolved config. This can be done only in the case when we already know the +// resolver IP address passed via options. func newBootstrapperResolved(upsURL *url.URL, options *Options) (*bootstrapper, error) { // get a host without port host, port, err := net.SplitHostPort(upsURL.Host) if err != nil { - return nil, fmt.Errorf("bootstrapper requires port in address %s", upsURL.String()) + return nil, fmt.Errorf("bootstrapper requires port in address %s", upsURL) } var resolverAddresses []string @@ -70,6 +83,10 @@ func newBootstrapperResolved(upsURL *url.URL, options *Options) (*bootstrapper, b := &bootstrapper{ URL: upsURL, options: options, + // Use the default capacity for the LRU cache. It may be useful to + // store several caches since the user may be routed to different + // servers in case there's load balancing on the server-side. + sessionsCache: tls.NewLRUClientSessionCache(0), } b.dialContext = b.createDialContext(resolverAddresses) b.resolvedConfig = b.createTLSConfig(host) @@ -77,9 +94,9 @@ func newBootstrapperResolved(upsURL *url.URL, options *Options) (*bootstrapper, return b, nil } -// newBootstrapper initializes a new bootstrapper instance -// address -- original resolver address string (i.e. tls://one.one.one.one:853) -// options -- Upstream customization options +// newBootstrapper initializes a new bootstrapper instance. u is the original +// resolver address string (i.e. tls://one.one.one.one:853), options is the +// upstream configuration options. func newBootstrapper(u *url.URL, options *Options) (b *bootstrapper, err error) { resolvers := []*Resolver{} if len(options.Bootstrap) != 0 { @@ -103,18 +120,31 @@ func newBootstrapper(u *url.URL, options *Options) (b *bootstrapper, err error) URL: u, resolvers: resolvers, options: options, + // Use the default capacity for the LRU cache. It may be useful to + // store several caches since the user may be routed to different + // servers in case there's load balancing on the server-side. + sessionsCache: tls.NewLRUClientSessionCache(0), }, nil } -// dialHandler specifies the dial function for creating unencrypted TCP connections. +// dialHandler describes the dial function for creating unencrypted network +// connections to the upstream server. Internally, this function will use the +// supplied bootstrap DNS servers to resolve the upstream's IP address and only +// then it will actually establish a connection. type dialHandler func(ctx context.Context, network, addr string) (net.Conn, error) -// will get usable IP address from Address field, and caches the result +// get is the main function of bootstrapper that does two crucial things. +// First, it creates an instance of a dialHandler function that should be used +// by the Upstream to establish a connection to the upstream DNS server. This +// dialHandler in a lazy manner resolves the DNS server IP address using the +// bootstrap DNS servers supplied to this bootstrapper instance. It will also +// create an instance of *tls.Config that should be used for establishing an +// encrypted connection for DoH/DoT/DoQ. func (n *bootstrapper) get() (*tls.Config, dialHandler, error) { - n.RLock() + n.guard.RLock() if n.dialContext != nil && n.resolvedConfig != nil { // fast path tlsConfig, dialContext := n.resolvedConfig, n.dialContext - n.RUnlock() + n.guard.RUnlock() return tlsConfig.Clone(), dialContext, nil } @@ -123,22 +153,24 @@ func (n *bootstrapper) get() (*tls.Config, dialHandler, error) { // // get a host without port - addr := n.URL - host, port, err := net.SplitHostPort(addr.Host) + u := n.URL + host, port, err := net.SplitHostPort(u.Host) if err != nil { - n.RUnlock() - return nil, nil, fmt.Errorf("bootstrapper requires port in address %s", addr.String()) + n.guard.RUnlock() + return nil, nil, fmt.Errorf("bootstrapper requires port in address %s", u) } - // if n.address's host is an IP, just use it right away + // if n.address's host is an IP, just use it right away. ip := net.ParseIP(host) if ip != nil { - n.RUnlock() + n.guard.RUnlock() - // Upgrade lock to protect n.resolved resolverAddress := net.JoinHostPort(host, port) - n.Lock() - defer n.Unlock() + + // Upgrade lock to protect n.resolvedConfig. + // TODO(ameshkov): rework, that's not how it should be done. + n.guard.Lock() + defer n.guard.Unlock() n.dialContext = n.createDialContext([]string{resolverAddress}) n.resolvedConfig = n.createTLSConfig(host) @@ -148,7 +180,7 @@ func (n *bootstrapper) get() (*tls.Config, dialHandler, error) { // Don't lock anymore (we can launch multiple lookup requests at a time) // Otherwise, it might mess with the timeout specified for the Upstream // See here: https://github.com/AdguardTeam/dnsproxy/issues/15 - n.RUnlock() + n.guard.RUnlock() // // if it's a hostname @@ -182,24 +214,26 @@ func (n *bootstrapper) get() (*tls.Config, dialHandler, error) { return nil, nil, fmt.Errorf("couldn't find any suitable IP address for host %s", host) } - n.Lock() - defer n.Unlock() + n.guard.Lock() + defer n.guard.Unlock() n.dialContext = n.createDialContext(resolved) n.resolvedConfig = n.createTLSConfig(host) return n.resolvedConfig, n.dialContext, nil } -// createTLSConfig creates a client TLS config +// createTLSConfig creates a client TLS config that will be used to establish +// an encrypted connection for DoH/DoT/DoQ. func (n *bootstrapper) createTLSConfig(host string) *tls.Config { tlsConfig := &tls.Config{ ServerName: host, RootCAs: RootCAs, CipherSuites: CipherSuites, + ClientSessionCache: n.sessionsCache, MinVersion: tls.VersionTLS12, InsecureSkipVerify: n.options.InsecureSkipVerify, VerifyPeerCertificate: n.options.VerifyServerCertificate, - ClientSessionCache: tls.NewLRUClientSessionCache(1), + VerifyConnection: n.options.VerifyConnection, } // Depending on the URL scheme, we choose what ALPN will be advertised by @@ -210,16 +244,26 @@ func (n *bootstrapper) createTLSConfig(host string) *tls.Config { // // See https://github.com/ameshkov/dnslookup/issues/19. case "https": - tlsConfig.NextProtos = []string{http2.NextProtoTLS, "http/1.1"} + httpVersions := n.options.HTTPVersions + if httpVersions == nil { + httpVersions = DefaultHTTPVersions + } + + var nextProtos []string + for _, v := range httpVersions { + nextProtos = append(nextProtos, string(v)) + } + + tlsConfig.NextProtos = nextProtos case "quic": tlsConfig.NextProtos = compatProtoDQ - tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(10) } return tlsConfig } -// createDialContext returns dialContext function that tries to establish connection with all given addresses one by one +// createDialContext returns a dialHandler function that tries to establish the +// connection to each of the provided addresses one by one. func (n *bootstrapper) createDialContext(addresses []string) (dialContext dialHandler) { dialer := &net.Dialer{ Timeout: n.options.Timeout, @@ -240,14 +284,23 @@ func (n *bootstrapper) createDialContext(addresses []string) (dialContext dialHa conn, err := dialer.DialContext(ctx, network, resolverAddress) elapsed := time.Since(start) if err == nil { - log.Tracef("dialer has successfully initialized connection to %s in %s", resolverAddress, elapsed) + log.Tracef( + "dialer has successfully initialized connection to %s in %s", + resolverAddress, + elapsed, + ) return conn, nil } errs = append(errs, err) - log.Tracef("dialer failed to initialize connection to %s, in %s, cause: %s", resolverAddress, elapsed, err) + log.Tracef( + "dialer failed to initialize connection to %s, in %s, cause: %s", + resolverAddress, + elapsed, + err, + ) } return nil, errors.List("all dialers failed", errs...) diff --git a/upstream/upstream.go b/upstream/upstream.go index 235fcbf9..586526d6 100644 --- a/upstream/upstream.go +++ b/upstream/upstream.go @@ -1,7 +1,9 @@ -// Package upstream implements DNS clients for all known DNS encryption protocols +// Package upstream implements DNS clients for all known DNS encryption +// protocols. package upstream import ( + "crypto/tls" "crypto/x509" "fmt" "net" @@ -17,13 +19,17 @@ import ( "github.com/miekg/dns" ) -// Upstream is an interface for a DNS resolver +// Upstream is an interface for a DNS resolver. type Upstream interface { + // Exchange sends the DNS query m to this upstream and returns the response + // that has been received or an error if something went wrong. Exchange(m *dns.Msg) (*dns.Msg, error) + // Address returns the address of the upstream DNS resolver. Address() string } -// Options for AddressToUpstream func +// Options for AddressToUpstream func. With these options we can configure the +// upstream properties. type Options struct { // Bootstrap is a list of DNS servers to be used to resolve // DNS-over-HTTPS/DNS-over-TLS hostnames. Plain DNS, DNSCrypt, or @@ -42,17 +48,55 @@ type Options struct { // InsecureSkipVerify disables verifying the server's certificate. InsecureSkipVerify bool - // VerifyServerCertificate used to be set to crypto/tls - // Config.VerifyPeerCertificate for DNS-over-HTTPS, DNS-over-QUIC, - // DNS-over-TLS. + // HTTPVersions is a list of HTTP versions that should be supported by the + // DNS-over-HTTPS client. If not set, HTTP/1.1 and HTTP/2 will be used. + HTTPVersions []HTTPVersion + + // VerifyServerCertificate is used to set the VerifyPeerCertificate property + // of the *tls.Config for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS. VerifyServerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error + // VerifyConnection is used to set the VerifyConnection property + // of the *tls.Config for DNS-over-HTTPS, DNS-over-QUIC, and DNS-over-TLS. + VerifyConnection func(state tls.ConnectionState) error + // VerifyDNSCryptCertificate is the callback the DNSCrypt server certificate // will be passed to. It's called in dnsCrypt.exchangeDNSCrypt. // Upstream.Exchange method returns any error caused by it. VerifyDNSCryptCertificate func(cert *dnscrypt.Cert) error } +// Clone copies o to a new struct. Note, that this is not a deep clone. +func (o *Options) Clone() (clone *Options) { + return &Options{ + Bootstrap: o.Bootstrap, + Timeout: o.Timeout, + ServerIPAddrs: o.ServerIPAddrs, + InsecureSkipVerify: o.InsecureSkipVerify, + HTTPVersions: o.HTTPVersions, + VerifyServerCertificate: o.VerifyServerCertificate, + VerifyConnection: o.VerifyConnection, + VerifyDNSCryptCertificate: o.VerifyDNSCryptCertificate, + } +} + +// HTTPVersion is an enumeration of the HTTP versions that we support. Values +// that we use in this enumeration are also used as ALPN values. +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" +) + +// DefaultHTTPVersions is the list of HTTPVersion that we use by default in +// the DNS-over-HTTPS client. +var DefaultHTTPVersions = []HTTPVersion{HTTPVersion11, HTTPVersion2} + const ( // defaultPortPlain is the default port for plain DNS. defaultPortPlain = 53 @@ -72,11 +116,12 @@ const ( // AddressToUpstream converts addr to an Upstream instance: // -// 8.8.8.8:53 or udp://dns.adguard.com for plain DNS; -// tcp://8.8.8.8:53 for plain DNS-over-TCP; -// tls://1.1.1.1 for DNS-over-TLS; -// https://dns.adguard.com/dns-query for DNS-over-HTTPS; -// sdns://... for DNS stamp, see https://dnscrypt.info/stamps-specifications. +// - 8.8.8.8:53 or udp://dns.adguard.com for plain DNS; +// - tcp://8.8.8.8:53 for plain DNS-over-TCP; +// - tls://1.1.1.1 for DNS-over-TLS; +// - https://dns.adguard.com/dns-query for DNS-over-HTTPS; +// - h3://dns.google for DNS-over-HTTPS that only works with HTTP/3; +// - sdns://... for DNS stamp, see https://dnscrypt.info/stamps-specifications. // // opts are applied to the u. nil is a valid value for opts. func AddressToUpstream(addr string, opts *Options) (u Upstream, err error) { @@ -129,6 +174,10 @@ func urlToUpstream(uu *url.URL, opts *Options) (u Upstream, err error) { return newDoQ(uu, opts) case "tls": return newDoT(uu, opts) + case "h3": + opts.HTTPVersions = []HTTPVersion{HTTPVersion3} + uu.Scheme = "https" + return newDoH(uu, opts) case "https": return newDoH(uu, opts) default: @@ -190,11 +239,10 @@ func logBegin(upstreamAddress string, req *dns.Msg) { qtype := "" target := "" if len(req.Question) != 0 { - qtype = dns.TypeToString[req.Question[0].Qtype] + qtype = dns.Type(req.Question[0].Qtype).String() target = req.Question[0].Name } - log.Debug("%s: sending request %s %s", - upstreamAddress, qtype, target) + log.Debug("%s: sending request %s %s", upstreamAddress, qtype, target) } // Write to log about the result of DNS request @@ -203,6 +251,5 @@ func logFinish(upstreamAddress string, err error) { if err != nil { status = err.Error() } - log.Debug("%s: response: %s", - upstreamAddress, status) + log.Debug("%s: response: %s", upstreamAddress, status) } diff --git a/upstream/upstream_dnscrypt.go b/upstream/upstream_dnscrypt.go index e3c05a83..d0a0c04d 100644 --- a/upstream/upstream_dnscrypt.go +++ b/upstream/upstream_dnscrypt.go @@ -13,9 +13,8 @@ import ( "github.com/miekg/dns" ) -// -// DNSCrypt -// +// dnsCrypt is a struct that implements the Upstream interface for the DNSCrypt +// protocol. type dnsCrypt struct { boot *bootstrapper client *dnscrypt.Client // DNSCrypt client properties @@ -27,8 +26,10 @@ type dnsCrypt struct { // type check var _ Upstream = (*dnsCrypt)(nil) +// Address implements the Upstream interface for *dnsCrypt. func (p *dnsCrypt) Address() string { return p.boot.URL.String() } +// Exchange implements the Upstream interface for *dnsCrypt. func (p *dnsCrypt) Exchange(m *dns.Msg) (*dns.Msg, error) { reply, err := p.exchangeDNSCrypt(m) diff --git a/upstream/upstream_doh.go b/upstream/upstream_doh.go index b71e64e9..78a93afa 100644 --- a/upstream/upstream_doh.go +++ b/upstream/upstream_doh.go @@ -1,9 +1,12 @@ package upstream import ( + "context" + "crypto/tls" "encoding/base64" "fmt" "io" + "net" "net/http" "net/url" "os" @@ -11,6 +14,9 @@ import ( "time" "github.com/AdguardTeam/golibs/errors" + "github.com/AdguardTeam/golibs/log" + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/http3" "github.com/miekg/dns" "golang.org/x/net/http2" ) @@ -34,7 +40,8 @@ const ( dohMaxIdleConns = 1 ) -// dnsOverHTTPS represents DNS-over-HTTPS upstream. +// dnsOverHTTPS is a struct that implements the Upstream interface for the +// DNS-over-HTTPS protocol. type dnsOverHTTPS struct { boot *bootstrapper @@ -43,6 +50,10 @@ type dnsOverHTTPS struct { // needed. Clients are safe for concurrent use by multiple goroutines. client *http.Client clientGuard sync.Mutex + + // quicConfig is the QUIC configuration that is used if HTTP/3 is enabled + // for this upstream. + quicConfig *quic.Config } // type check @@ -58,11 +69,25 @@ func newDoH(uu *url.URL, opts *Options) (u Upstream, err error) { return nil, fmt.Errorf("creating https bootstrapper: %w", err) } - return &dnsOverHTTPS{boot: b}, nil + return &dnsOverHTTPS{ + 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), + }, + }, nil } +// Address implements the Upstream interface for *dnsOverHTTPS. 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) { client, err := p.getClient() if err != nil { @@ -146,8 +171,8 @@ func (p *dnsOverHTTPS) getClient() (c *http.Client, err error) { return p.client, nil } - // Timeout can be exceeded while waiting for the lock - // This happens quite often on mobile devices + // Timeout can be exceeded while waiting for the lock. This happens quite + // often on mobile devices. elapsed := time.Since(startTime) if p.boot.options.Timeout > 0 && elapsed > p.boot.options.Timeout { return nil, fmt.Errorf("timeout exceeded: %s", elapsed) @@ -158,6 +183,10 @@ func (p *dnsOverHTTPS) getClient() (c *http.Client, err error) { return p.client, err } +// createClient creates a new *http.Client instance. The HTTP protocol version +// will depend on whether HTTP3 is allowed and provided by this upstream. Note, +// that we'll attempt to establish a QUIC connection when creating the client in +// order to check whether HTTP3 is supported. func (p *dnsOverHTTPS) createClient() (*http.Client, error) { transport, err := p.createTransport() if err != nil { @@ -175,14 +204,32 @@ func (p *dnsOverHTTPS) createClient() (*http.Client, error) { } // 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. -func (p *dnsOverHTTPS) createTransport() (*http.Transport, error) { +// 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) { tlsConfig, dialContext, err := p.boot.get() if err != nil { return nil, fmt.Errorf("bootstrapping %s: %w", p.boot.URL, err) } + // First, we attempt to create an HTTP3 transport. If the probe QUIC + // connection is established successfully, we'll be using HTTP3 for this + // upstream. + transportH3, err := p.createTransportH3(tlsConfig, dialContext) + if err == nil { + log.Debug("using HTTP/3 for this upstream: QUIC was faster") + return transportH3, nil + } + + log.Debug("using HTTP/2 for this upstream: %v", err) + + if !p.supportsHTTP() { + return nil, errors.Error("HTTP1/1 and HTTP2 are not supported by this upstream") + } + transport := &http.Transport{ TLSClientConfig: tlsConfig, DisableCompression: true, @@ -210,3 +257,185 @@ func (p *dnsOverHTTPS) createTransport() (*http.Transport, error) { return transport, nil } + +// 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 +// will create the *http3.RoundTripper instance. +func (p *dnsOverHTTPS) createTransportH3( + tlsConfig *tls.Config, + dialContext dialHandler, +) (roundTripper *http3.RoundTripper, err error) { + if !p.supportsH3() { + return nil, errors.Error("HTTP3 support is not enabled") + } + + addr, err := p.probeH3(tlsConfig, dialContext) + if err != nil { + return nil, err + } + + return &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) + }, + DisableCompression: true, + TLSClientConfig: tlsConfig, + QuicConfig: p.quicConfig, + }, nil +} + +// probeH3 runs a test to check whether QUIC is faster than TLS for this +// 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, + dialContext dialHandler, +) (addr string, err error) { + // We're using bootstrapped address instead of what's passed to the function + // it does not create an actual connection, but it helps us determine + // what IP is actually reachable (when there are v4/v6 addresses). + rawConn, err := dialContext(context.Background(), "udp", "") + if err != nil { + return "", fmt.Errorf("failed to dial: %w", err) + } + // It's never actually used. + _ = rawConn.Close() + + udpConn, ok := rawConn.(*net.UDPConn) + if !ok { + return "", fmt.Errorf("not a UDP connection to %s", p.Address()) + } + + addr = udpConn.RemoteAddr().String() + + // Avoid spending time on probing if this upstream only supports HTTP/3. + if p.supportsH3() && !p.supportsHTTP() { + return addr, nil + } + + // Use a new *tls.Config with empty session cache for probe connections. + // Surprisingly, this is really important since otherwise it invalidates + // the existing cache. + // TODO(ameshkov): figure out why the sessions cache invalidates here. + probeTLSCfg := tlsConfig.Clone() + probeTLSCfg.ClientSessionCache = nil + + // 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. + chQuic := make(chan error, 1) + chTLS := make(chan error, 1) + go p.probeQUIC(addr, probeTLSCfg, chQuic) + go p.probeTLS(dialContext, probeTLSCfg, chTLS) + + select { + case quicErr := <-chQuic: + if quicErr != nil { + // QUIC failed, return error since HTTP3 was not preferred. + return "", quicErr + } + + // Return immediately, QUIC was faster. + return addr, quicErr + case tlsErr := <-chTLS: + if tlsErr != nil { + // Return immediately, TLS failed. + log.Debug("probing TLS: %v", tlsErr) + return addr, nil + } + + return "", errors.Error("TLS was faster than QUIC, prefer it") + } +} + +// 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 := p.boot.options.Timeout + if timeout == 0 { + timeout = dialTimeout + } + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout)) + defer cancel() + + conn, err := quic.DialAddrEarlyContext(ctx, addr, tlsConfig, p.quicConfig) + if err != nil { + ch <- fmt.Errorf("opening QUIC connection to %s: %w", p.Address(), err) + return + } + + // Ignore the error since there's no way we can use it for anything useful. + _ = conn.CloseWithError(QUICCodeNoError, "") + + ch <- nil + + elapsed := time.Now().Sub(startTime) + log.Debug("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) { + startTime := time.Now() + + conn, err := 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.Debug("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 == 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 == HTTPVersion11 || v == HTTPVersion2 { + return true + } + } + + return false +} + +// supportedHTTPVersions returns the list of supported HTTP versions. +func (p *dnsOverHTTPS) supportedHTTPVersions() (v []HTTPVersion) { + v = p.boot.options.HTTPVersions + if v == nil { + v = DefaultHTTPVersions + } + + return v +} diff --git a/upstream/upstream_doh_test.go b/upstream/upstream_doh_test.go new file mode 100644 index 00000000..16a9a09f --- /dev/null +++ b/upstream/upstream_doh_test.go @@ -0,0 +1,264 @@ +package upstream + +import ( + "context" + "crypto/tls" + "encoding/base64" + "fmt" + "net" + "net/http" + "testing" + "time" + + "github.com/lucas-clemente/quic-go" + "github.com/lucas-clemente/quic-go/http3" + "github.com/miekg/dns" + "github.com/stretchr/testify/require" +) + +func TestUpstreamDoH(t *testing.T) { + testCases := []struct { + name string + http3Enabled bool + httpVersions []HTTPVersion + delayHandshakeH3 time.Duration + delayHandshakeH2 time.Duration + expectedProtocol HTTPVersion + }{{ + name: "http1.1_h2", + http3Enabled: false, + httpVersions: []HTTPVersion{HTTPVersion11, HTTPVersion2}, + expectedProtocol: HTTPVersion2, + }, { + name: "fallback_to_http2", + http3Enabled: false, + httpVersions: []HTTPVersion{HTTPVersion3, HTTPVersion2}, + expectedProtocol: HTTPVersion2, + }, { + name: "http3", + http3Enabled: true, + httpVersions: []HTTPVersion{HTTPVersion3}, + expectedProtocol: HTTPVersion3, + }, { + name: "race_http3_faster", + http3Enabled: true, + httpVersions: []HTTPVersion{HTTPVersion3, HTTPVersion2}, + delayHandshakeH2: time.Second, + expectedProtocol: HTTPVersion3, + }, { + name: "race_http2_faster", + http3Enabled: true, + httpVersions: []HTTPVersion{HTTPVersion3, HTTPVersion2}, + delayHandshakeH3: time.Second, + expectedProtocol: HTTPVersion2, + }} + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + srv := startDoHServer(t, testDoHServerOptions{ + http3Enabled: tc.http3Enabled, + delayHandshakeH2: tc.delayHandshakeH2, + delayHandshakeH3: tc.delayHandshakeH3, + }) + t.Cleanup(srv.Shutdown) + + // Create a DNS-over-HTTPS upstream. + address := fmt.Sprintf("https://%s/dns-query", srv.addr) + + var lastState tls.ConnectionState + u, err := AddressToUpstream( + address, + &Options{ + InsecureSkipVerify: true, + HTTPVersions: tc.httpVersions, + VerifyConnection: func(state tls.ConnectionState) (err error) { + if state.NegotiatedProtocol != string(tc.expectedProtocol) { + return fmt.Errorf( + "expected %s, got %s", + tc.expectedProtocol, + state.NegotiatedProtocol, + ) + } + lastState = state + return nil + }, + }, + ) + require.NoError(t, err) + + // Test that it responds properly. + for i := 0; i < 10; i++ { + checkUpstream(t, u, address) + } + + doh := u.(*dnsOverHTTPS) + + // Trigger re-connection. + doh.client = nil + + // Force it to establish the connection again. + checkUpstream(t, u, address) + + // Check that TLS session was resumed properly. + require.True(t, lastState.DidResume) + }) + } +} + +// testDoHServerOptions allows customizing testDoHServer behavior. +type testDoHServerOptions struct { + http3Enabled bool + delayHandshakeH2 time.Duration + delayHandshakeH3 time.Duration +} + +// testDoHServer is an instance of a test DNS-over-HTTPS server that we use +// for tests. +type testDoHServer 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 + + // server is an HTTP/1.1 and HTTP/2 server. + server *http.Server + + // serverH3 is an HTTP/3 server. + serverH3 *http3.Server +} + +// Shutdown stops the DOH server. +func (s *testDoHServer) Shutdown() { + if s.server != nil { + _ = s.server.Shutdown(context.Background()) + } + + if s.serverH3 != nil { + _ = s.serverH3.Close() + } +} + +// startDoHServer starts a new DNS-over-HTTPS server on a random port and +// returns the instance of this server. Depending on whether http3Enabled is +// set to true or false it will or will not initialize a HTTP/3 server. +func startDoHServer( + t *testing.T, + opts testDoHServerOptions, +) (s *testDoHServer) { + tlsConfig := createServerTLSConfig(t, "127.0.0.1") + handler := createDoHHandler() + + // Step one is to create a regular HTTP server, we'll always have it + // running. + server := &http.Server{ + Handler: handler, + } + + // Listen TCP first. + tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:0") + require.NoError(t, err) + + tcpListen, err := net.ListenTCP("tcp", tcpAddr) + require.NoError(t, err) + + tlsConfigH2 := tlsConfig.Clone() + tlsConfigH2.NextProtos = []string{string(HTTPVersion2), string(HTTPVersion11)} + tlsConfigH2.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { + if opts.delayHandshakeH2 > 0 { + time.Sleep(opts.delayHandshakeH2) + } + return nil, nil + } + tlsListen := tls.NewListener(tcpListen, tlsConfigH2) + + // Run the H1/H2 server. + go server.Serve(tlsListen) + + // Get the real address that the listener now listens to. + tcpAddr = tcpListen.Addr().(*net.TCPAddr) + + var serverH3 *http3.Server + + if opts.http3Enabled { + tlsConfigH3 := tlsConfig.Clone() + tlsConfigH3.NextProtos = []string{string(HTTPVersion3)} + tlsConfigH3.GetConfigForClient = func(_ *tls.ClientHelloInfo) (*tls.Config, error) { + if opts.delayHandshakeH3 > 0 { + time.Sleep(opts.delayHandshakeH3) + } + return nil, nil + } + + serverH3 = &http3.Server{ + TLSConfig: tlsConfig.Clone(), + QuicConfig: &quic.Config{}, + Handler: handler, + } + + // Listen UDP for the H3 server. Reuse the same port as was used for the + // TCP listener. + udpAddr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", tcpAddr.Port)) + require.NoError(t, err) + + udpListen, err := net.ListenUDP("udp", udpAddr) + require.NoError(t, err) + + // Run the H3 server. + go serverH3.Serve(udpListen) + } + + return &testDoHServer{ + tlsConfig: tlsConfig, + server: server, + serverH3: serverH3, + // Save the address that the server listens to. + addr: tcpAddr.String(), + } +} + +// createDoHHandler returns a very simple http.Handler that reads the incoming +// request and returns with a test message. +func createDoHHandler() (h http.Handler) { + mux := http.NewServeMux() + mux.HandleFunc("/dns-query", func(w http.ResponseWriter, r *http.Request) { + dnsParam := r.URL.Query().Get("dns") + buf, err := base64.RawURLEncoding.DecodeString(dnsParam) + if err != nil { + http.Error( + w, + fmt.Sprintf("internal error: %s", err), + http.StatusInternalServerError, + ) + return + } + + m := &dns.Msg{} + err = m.Unpack(buf) + if err != nil { + http.Error( + w, + fmt.Sprintf("internal error: %s", err), + http.StatusInternalServerError, + ) + return + } + + resp := respondToTestMessage(m) + + buf, err = resp.Pack() + if err != nil { + http.Error( + w, + fmt.Sprintf("internal error: %s", err), + http.StatusInternalServerError, + ) + return + } + + w.Header().Set("Content-Type", "application/dns-message") + _, err = w.Write(buf) + }) + + return mux +} diff --git a/upstream/upstream_dot.go b/upstream/upstream_dot.go index 4b31d330..380478d3 100644 --- a/upstream/upstream_dot.go +++ b/upstream/upstream_dot.go @@ -11,9 +11,8 @@ import ( "github.com/miekg/dns" ) -// -// DNS-over-TLS -// +// dnsOverTLS is a struct that implements the Upstream interface for the +// DNS-over-TLS protocol. type dnsOverTLS struct { boot *bootstrapper pool *TLSPool @@ -37,8 +36,10 @@ func newDoT(uu *url.URL, opts *Options) (u Upstream, err error) { return &dnsOverTLS{boot: b}, nil } +// Address implements the Upstream interface for *dnsOverTLS. func (p *dnsOverTLS) Address() string { return p.boot.URL.String() } +// Exchange implements the Upstream interface for *dnsOverTLS. func (p *dnsOverTLS) Exchange(m *dns.Msg) (reply *dns.Msg, err error) { var pool *TLSPool p.RLock() diff --git a/upstream/upstream_plain.go b/upstream/upstream_plain.go index 1b6d1781..f5f4efd6 100644 --- a/upstream/upstream_plain.go +++ b/upstream/upstream_plain.go @@ -8,9 +8,8 @@ import ( "github.com/miekg/dns" ) -// -// plain DNS -// +// plainDNS is a struct that implements the Upstream interface for the regular +// DNS protocol. type plainDNS struct { address string timeout time.Duration @@ -31,7 +30,7 @@ func newPlain(uu *url.URL, timeout time.Duration, preferTCP bool) (u *plainDNS) } } -// Address returns the original address that we've put in initially, not resolved one +// Address implements the Upstream interface for *plainDNS. func (p *plainDNS) Address() string { if p.preferTCP { return "tcp://" + p.address @@ -40,6 +39,7 @@ func (p *plainDNS) Address() string { return p.address } +// Exchange implements the Upstream interface for *plainDNS. func (p *plainDNS) Exchange(m *dns.Msg) (*dns.Msg, error) { if p.preferTCP { tcpClient := dns.Client{Net: "tcp", Timeout: p.timeout} diff --git a/upstream/upstream_pool.go b/upstream/upstream_pool.go index 276fa04f..9c3ddd3b 100644 --- a/upstream/upstream_pool.go +++ b/upstream/upstream_pool.go @@ -11,35 +11,40 @@ import ( "github.com/AdguardTeam/golibs/log" ) +// dialTimeout is the global timeout for establishing a TLS connection. +// TODO(ameshkov): use bootstrap timeout instead. const dialTimeout = 10 * time.Second // TLSPool is a connections pool for the DNS-over-TLS Upstream. // // Example: -// pool := TLSPool{Address: "tls://1.1.1.1:853"} -// netConn, err := pool.Get() -// if err != nil {panic(err)} -// c := dns.Conn{Conn: netConn} -// q := dns.Msg{} -// q.SetQuestion("google.com.", dns.TypeA) -// log.Println(q) -// err = c.WriteMsg(&q) -// if err != nil {panic(err)} -// r, err := c.ReadMsg() -// if err != nil {panic(err)} -// log.Println(r) -// pool.Put(c.Conn) +// +// pool := TLSPool{Address: "tls://1.1.1.1:853"} +// netConn, err := pool.Get() +// if err != nil {panic(err)} +// c := dns.Conn{Conn: netConn} +// q := dns.Msg{} +// q.SetQuestion("google.com.", dns.TypeA) +// log.Println(q) +// err = c.WriteMsg(&q) +// if err != nil {panic(err)} +// r, err := c.ReadMsg() +// if err != nil {panic(err)} +// log.Println(r) +// pool.Put(c.Conn) type TLSPool struct { boot *bootstrapper - // connections - conns []net.Conn - connsMutex sync.Mutex // protects conns + // conns is the list of connections available in the pool. + conns []net.Conn + // connsMutex protects conns. + connsMutex sync.Mutex } -// Get gets or creates a new TLS connection +// Get gets a connection from the pool (if there's one available) or creates +// a new TLS connection. func (n *TLSPool) Get() (net.Conn, error) { - // get the connection from the slice inside the lock + // Get the connection from the slice inside the lock. var c net.Conn n.connsMutex.Lock() num := len(n.conns) @@ -50,7 +55,7 @@ func (n *TLSPool) Get() (net.Conn, error) { } n.connsMutex.Unlock() - // if we got connection from the slice, update deadline and return it. + // If we got connection from the slice, update deadline and return it. if c != nil { err := c.SetDeadline(time.Now().Add(dialTimeout)) @@ -64,7 +69,7 @@ func (n *TLSPool) Get() (net.Conn, error) { return n.Create() } -// Create creates a new connection for the pool (but not puts it there) +// Create creates a new connection for the pool (but not puts it there). func (n *TLSPool) Create() (net.Conn, error) { tlsConfig, dialContext, err := n.boot.get() if err != nil { @@ -80,7 +85,7 @@ func (n *TLSPool) Create() (net.Conn, error) { return conn, nil } -// Put returns connection to the pool +// Put returns the connection to the pool. func (n *TLSPool) Put(c net.Conn) { if c == nil { return @@ -90,16 +95,18 @@ func (n *TLSPool) Put(c net.Conn) { n.connsMutex.Unlock() } -// tlsDial is basically the same as tls.DialWithDialer, but we will call our own dialContext function to get connection +// tlsDial is basically the same as tls.DialWithDialer, but we will call our own +// dialContext function to get connection. func tlsDial(dialContext dialHandler, network string, config *tls.Config) (*tls.Conn, error) { - // we're using bootstrapped address instead of what's passed to the function + // We're using bootstrapped address instead of what's passed + // to the function. rawConn, err := dialContext(context.Background(), network, "") 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 + // 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 { diff --git a/upstream/upstream_pool_test.go b/upstream/upstream_pool_test.go index fe59edfa..85b73ad3 100644 --- a/upstream/upstream_pool_test.go +++ b/upstream/upstream_pool_test.go @@ -1,48 +1,51 @@ package upstream import ( + "crypto/tls" "testing" "time" + + "github.com/stretchr/testify/require" ) func TestTLSPoolReconnect(t *testing.T) { + var lastState tls.ConnectionState u, err := AddressToUpstream( "tls://one.one.one.one", &Options{ Bootstrap: []string{"8.8.8.8:53"}, Timeout: timeout, + VerifyConnection: func(state tls.ConnectionState) error { + lastState = state + return nil + }, }, ) - if err != nil { - t.Fatalf("cannot create upstream: %s", err) - } + require.NoError(t, err) - // Send the first test message + // Send the first test message. req := createTestMessage() reply, err := u.Exchange(req) - if err != nil { - t.Fatalf("first DNS message failed: %s", err) - } + require.NoError(t, err) requireResponse(t, req, reply) - // Now let's close the pooled connection and return it back to the pool + // Now let's close the pooled connection and return it back to the pool. p := u.(*dnsOverTLS) conn, _ := p.pool.Get() conn.Close() p.pool.Put(conn) - // Send the second test message + // Send the second test message. req = createTestMessage() reply, err = u.Exchange(req) - if err != nil { - t.Fatalf("second DNS message failed: %s", err) - } + require.NoError(t, err) requireResponse(t, req, reply) // Now assert that the number of connections in the pool is not changed - if len(p.pool.conns) != 1 { - t.Fatal("wrong number of pooled connections") - } + require.Len(t, p.pool.conns, 1) + + // Check that the session was resumed on the last attempt. + require.True(t, lastState.DidResume) } func TestTLSPoolDeadLine(t *testing.T) { diff --git a/upstream/upstream_quic.go b/upstream/upstream_quic.go index e4ca6df3..eb98fd17 100644 --- a/upstream/upstream_quic.go +++ b/upstream/upstream_quic.go @@ -15,30 +15,37 @@ import ( ) const ( - // DoQCodeNoError is used when the connection or stream needs to be closed, + // QUICCodeNoError is used when the connection or stream needs to be closed, // but there is no error to signal. - DoQCodeNoError = quic.ApplicationErrorCode(0) - // DoQCodeInternalError signals that the DoQ implementation encountered + QUICCodeNoError = quic.ApplicationErrorCode(0) + // QUICCodeInternalError signals that the DoQ implementation encountered // an internal error and is incapable of pursuing the transaction or the // connection. - DoQCodeInternalError = quic.ApplicationErrorCode(1) - // DoQCodeProtocolError signals that the DoQ implementation encountered + QUICCodeInternalError = quic.ApplicationErrorCode(1) + // QUICCodeProtocolError signals that the DoQ implementation encountered // a protocol error and is forcibly aborting the connection. - DoQCodeProtocolError = quic.ApplicationErrorCode(2) + 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 + // KeepAlive field set to true This value is specified in + // https://pkg.go.dev/github.com/lucas-clemente/quic-go/internal/protocol#MaxKeepAliveInterval. + // + // TODO(ameshkov): Consider making it configurable. + QUICKeepAlivePeriod = time.Second * 20 ) -// -// dnsOverQUIC is a DNS-over-QUIC implementation according to the spec: -// https://www.rfc-editor.org/rfc/rfc9250.html -// +// 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 { // boot is a bootstrap DNS abstraction that is used to resolve the upstream // server's address and open a network connection to it. boot *bootstrapper - // tokenStore is a QUIC token store that is used across QUIC connections. - // Since the QUIC config is re-created when a connection is (re-)opened - // the tokenStore is instead saved as part of the dnsOverQUIC struct. - tokenStore quic.TokenStore + // quicConfig is the QUIC configuration that is used for establishing + // connections to the upstream. This configuration includes the TokenStore + // that needs to be stored for the lifetime of dnsOverQUIC since we can + // re-create the connection. + quicConfig *quic.Config // conn is the current active QUIC connection. It can be closed and // re-opened when needed. conn quic.Connection @@ -62,11 +69,24 @@ func newDoQ(uu *url.URL, opts *Options) (u Upstream, err error) { return nil, fmt.Errorf("creating quic bootstrapper: %w", err) } - return &dnsOverQUIC{boot: b, tokenStore: quic.NewLRUTokenStore(1, 10)}, nil + return &dnsOverQUIC{ + 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), + }, + }, nil } +// Address implements the Upstream interface for *dnsOverQUIC. 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) @@ -95,13 +115,13 @@ 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(DoQCodeInternalError) + p.closeConnWithError(QUICCodeInternalError) return nil, fmt.Errorf("open new stream to %s: %w", p.Address(), err) } _, err = stream.Write(proxyutil.AddPrefix(buf)) if err != nil { - p.closeConnWithError(DoQCodeInternalError) + p.closeConnWithError(QUICCodeInternalError) return nil, fmt.Errorf("failed to write to a QUIC stream: %w", err) } @@ -117,7 +137,7 @@ func (p *dnsOverQUIC) Exchange(m *dns.Msg) (res *dns.Msg, err error) { // 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(DoQCodeProtocolError) + p.closeConnWithError(QUICCodeProtocolError) } return res, err } @@ -152,7 +172,7 @@ func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) { } if conn != nil { // we're recreating the connection, let's create a new one. - _ = conn.CloseWithError(DoQCodeNoError, "") + _ = conn.CloseWithError(QUICCodeNoError, "") } p.RUnlock() @@ -162,11 +182,10 @@ func (p *dnsOverQUIC) getConnection(useCached bool) (quic.Connection, error) { 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 conn. + // 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 @@ -224,17 +243,8 @@ func (p *dnsOverQUIC) openConnection() (conn quic.Connection, err error) { } addr := udpConn.RemoteAddr().String() - quicConfig := &quic.Config{ - // Set the keep alive interval to 20s as it would be in the - // quic-go@v0.27.1 with KeepAlive field set to true. This value is - // specified in - // https://pkg.go.dev/github.com/lucas-clemente/quic-go/internal/protocol#MaxKeepAliveInterval. - // - // TODO(ameshkov): Consider making it configurable. - KeepAlivePeriod: 20 * time.Second, - TokenStore: p.tokenStore, - } - conn, err = quic.DialAddrEarlyContext(context.Background(), addr, tlsConfig, quicConfig) + + conn, err = quic.DialAddrEarlyContext(context.Background(), addr, tlsConfig, p.quicConfig) if err != nil { return nil, fmt.Errorf("opening quic connection to %s: %w", p.Address(), err) } diff --git a/upstream/upstream_quic_test.go b/upstream/upstream_quic_test.go index f5d2ac5b..0a2fe21f 100644 --- a/upstream/upstream_quic_test.go +++ b/upstream/upstream_quic_test.go @@ -1,6 +1,7 @@ package upstream import ( + "crypto/tls" "testing" "github.com/lucas-clemente/quic-go" @@ -10,7 +11,16 @@ import ( func TestUpstreamDoQ(t *testing.T) { // Create a DNS-over-QUIC upstream address := "quic://dns.adguard.com" - u, err := AddressToUpstream(address, &Options{InsecureSkipVerify: true}) + var lastState tls.ConnectionState + u, err := AddressToUpstream( + address, + &Options{ + VerifyConnection: func(state tls.ConnectionState) error { + lastState = state + return nil + }, + }, + ) require.NoError(t, err) uq := u.(*dnsOverQUIC) @@ -23,8 +33,17 @@ func TestUpstreamDoQ(t *testing.T) { if conn == nil { conn = uq.conn } else { - // This way we test that the conn is properly reused + // This way we test that the conn is properly reused. require.Equal(t, conn, uq.conn) } } + + // Close the connection (make sure that we re-establish the connection). + _ = conn.CloseWithError(quic.ApplicationErrorCode(0), "") + + // Try to establish it again. + checkUpstream(t, u, address) + + // Make sure that the session has been resumed. + require.True(t, lastState.DidResume) } diff --git a/upstream/upstream_test.go b/upstream/upstream_test.go index 4a0901c4..dcf9e19a 100644 --- a/upstream/upstream_test.go +++ b/upstream/upstream_test.go @@ -1,8 +1,16 @@ package upstream import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" "io" + "math/big" "net" "net/url" "os" @@ -192,6 +200,10 @@ func TestUpstreams(t *testing.T) { // Cloudflare DNS address: "quic://dns-unfiltered.adguard.com:784", bootstrap: []string{}, + }, { + // Google DNS (HTTP3) + address: "h3://dns.google/dns-query", + bootstrap: []string{}, }} for _, test := range upstreams { t.Run(test.address, func(t *testing.T) { @@ -237,6 +249,10 @@ func TestAddressToUpstream(t *testing.T) { addr: "https://one.one.one.one", opt: opt, want: "https://one.one.one.one:443", + }, { + addr: "h3://one.one.one.one", + opt: opt, + want: "https://one.one.one.one:443", }} for _, tc := range testCases { @@ -405,45 +421,6 @@ func TestUpstreamsWithServerIP(t *testing.T) { } } -func checkUpstream(t *testing.T, u Upstream, addr string) { - t.Helper() - - req := createTestMessage() - reply, err := u.Exchange(req) - require.NoErrorf(t, err, "couldn't talk to upstream %s", addr) - - requireResponse(t, req, reply) -} - -func createTestMessage() *dns.Msg { - return createHostTestMessage("google-public-dns-a.google.com") -} - -func createHostTestMessage(host string) (req *dns.Msg) { - return &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Id: dns.Id(), - RecursionDesired: true, - }, - Question: []dns.Question{{ - Name: dns.Fqdn(host), - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }}, - } -} - -func requireResponse(t *testing.T, req, reply *dns.Msg) { - require.NotNil(t, reply) - require.Lenf(t, reply.Answer, 1, "wrong number of answers: %d", len(reply.Answer)) - require.Equal(t, req.Id, reply.Id) - - a, ok := reply.Answer[0].(*dns.A) - require.Truef(t, ok, "wrong answer type: %v", reply.Answer[0]) - - require.Equalf(t, net.IPv4(8, 8, 8, 8), a.A.To16(), "wrong answer: %v", a.A) -} - func TestAddPort(t *testing.T) { testCases := []struct { name string @@ -498,3 +475,131 @@ func TestAddPort(t *testing.T) { }) } } + +func checkUpstream(t *testing.T, u Upstream, addr string) { + t.Helper() + + req := createTestMessage() + reply, err := u.Exchange(req) + require.NoErrorf(t, err, "couldn't talk to upstream %s", addr) + + requireResponse(t, req, reply) +} + +func createTestMessage() (m *dns.Msg) { + return createHostTestMessage("google-public-dns-a.google.com") +} + +func respondToTestMessage(m *dns.Msg) (resp *dns.Msg) { + resp = &dns.Msg{} + resp.SetReply(m) + resp.Answer = append(resp.Answer, &dns.A{ + A: net.IPv4(8, 8, 8, 8), + Hdr: dns.RR_Header{ + Name: "google-public-dns-a.google.com.", + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 100, + }, + }) + + return resp +} + +func createHostTestMessage(host string) (req *dns.Msg) { + return &dns.Msg{ + MsgHdr: dns.MsgHdr{ + Id: dns.Id(), + RecursionDesired: true, + }, + Question: []dns.Question{{ + Name: dns.Fqdn(host), + Qtype: dns.TypeA, + Qclass: dns.ClassINET, + }}, + } +} + +func requireResponse(t *testing.T, req, reply *dns.Msg) { + require.NotNil(t, reply) + require.Lenf(t, reply.Answer, 1, "wrong number of answers: %d", len(reply.Answer)) + require.Equal(t, req.Id, reply.Id) + + a, ok := reply.Answer[0].(*dns.A) + require.Truef(t, ok, "wrong answer type: %v", reply.Answer[0]) + + require.Equalf(t, net.IPv4(8, 8, 8, 8), a.A.To16(), "wrong answer: %v", a.A) +} + +// createServerTLSConfig creates a test server TLS configuration. It returns +// a *tls.Config that can be used for both the server and the client. +func createServerTLSConfig(t *testing.T, tlsServerName string) (tlsConfig *tls.Config) { + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + require.NoError(t, err) + + notBefore := time.Now() + notAfter := notBefore.Add(5 * 365 * time.Hour * 24) + + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"AdGuard Tests"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + template.DNSNames = append(template.DNSNames, tlsServerName) + + derBytes, err := x509.CreateCertificate( + rand.Reader, + &template, + &template, + publicKey(privateKey), + privateKey, + ) + require.NoError(t, err) + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) + keyPem := pem.EncodeToMemory( + &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }, + ) + + cert, err := tls.X509KeyPair(certPem, keyPem) + require.NoError(t, err) + + roots := x509.NewCertPool() + roots.AppendCertsFromPEM(certPem) + + tlsConfig = &tls.Config{ + Certificates: []tls.Certificate{cert}, + ServerName: tlsServerName, + RootCAs: roots, + MinVersion: tls.VersionTLS12, + } + + return tlsConfig +} + +// publicKey extracts the public key from the specified private key. +func publicKey(priv any) (pub any) { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &k.PublicKey + case *ecdsa.PrivateKey: + return &k.PublicKey + default: + return nil + } +} diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/body.go b/vendor/github.com/lucas-clemente/quic-go/http3/body.go new file mode 100644 index 0000000000000000000000000000000000000000..d6e704ebcb169646fb06c040c6e7b4c1a01ce3dd GIT binary patch literal 2970 zcmXR&OwLYBPgTe$DJd}K;>ye|$S*2U(BR@!O3u$KNv$YR;^I`w%m<NqsU=EWT%1bj znI##eNqWiox%xS!$%)0f$vLUHsd*)-`h}&L$-3$JN?e*;T>AP7AsML(9w8wC!6ikh ziMgpo3W+&6`Q^n5C5hRYdFcxIWgsE23VmaRVz44Tu%^tslGLKK#N<?k%wmPi+ybci zDGEuIRv?456w>mG6iPBu6^c`f%2JE0z-*8g^ny|gOH+$W^qlfjDp8dt=VYelm7pjs zF38U-PKB%W&jXtSQK(RynUbml7JxWSp*%AuM<FRyAvq_%I5h?Agz}=ylFYnx9fi`o zoYdlCuxgk5yb@=S5~x!kzE=p!FU?B{DatJHFDOwcPb^j_PA$;`xiCB<HBTWC>VJ@j zOA@nF^T45_qmWsm4ly-3F()UrNWEC0C>8AL%%sem%#uoll6;7Jz*a*Xs>fAQS%4Pu zh!Cyj;zY!WhNc3<d0e$z5M2<ib5UwyNq&(6iZ=g()I5l^hNglBD6aJ&TpfkfqN4mF zO)gG^lHkg`WDRH%(sPC+C49O<^D-fZA<RzAOMwWXnh#fr*9t6}ee#nNa~xAriZnD8 z@={CmKuj*qpw!&_l2i~IS-6&qt1PidAs!k$u<)=|0DIdxKQAvexg;|`Pa`ig2a?zw z6+AMt5|gt*X&REuGr)XsN>4+I9))C3;DUq;5{pU{^3xO&A*KeW78hsc=P9HW<>x9S zf^(!^5G1xCX%w7XJWD`nq!g3_lPVR$Q<Fl967!0|xfYZbKxU_+3PVywu|i^63CM8@ z#ZY@P5{ng*Qd9F3Qj1FxlX5bPGg4DvX$S1|RI~&Di4AC2z>^M?4@yDMJfD=GQVH=F za+XxcFGBMZB*lSM7nc;3CYLBwb8!}z6hVUnTp)09ah4|*LxVO&At^sUM?qg7lz9}& zK^X^;mO;S+&Lt@zr+{KlgNrj>Av0euC^az!oFj`AY!%c%_Epz{q!4;jixiNgG`YCa zO7oHx@>0v8c^FihXn<S>2?9+8Es!Z7=N6@wlosVdO)V}dvI1q~T96CCMrag4lxl*D zIf!m}$bt3iA$!kOp`@rZm5UQ<YLQ-XNf8#a$`gwrM!~{GLlY7*)ks>A%*A2^C`>ex z6ry93Doau|6*MyQN?_Rx>@0}4zzzV1pGJ}<7E{2Xs-X$V|6qL}7wb7E<|U`*fUN-M z7l>nBL4|~KeoCqaxVV4@Ektc<il!#q?Rl9wAiKb&7$g%VCgr4p;s6x5&~yTd04~nt z{JcC!azW%tQ2NNv%c)ezOHEBlO#u~=@HzyZNx8T<_4O6N%8FA<z=b)eK7l4!a1omd zRsyQTk~0$X@=|laDGXGyr$EvkNJD8cD3=r~q~zzNDwJoIWGIwmWWr17R4cF<shK4i zsYMV?;81~vSg{T$Q-ISkEF?3F6(EUN4-_1QE+AtSzyRb38(nA!fD&*~Dp(q<6OuDP zp$1R*(6mtm)@-Ywh8mI}1LBbqWhyvtV5mXK3Xo6%#RF1a&{4n^2Re`dL36XFf)=U` z;3NY{YUu9c;^YKHv6TXdtmfk61eFZn+@-_C$yo~`paEb7whJTxa)}klzaWMV7bj{y zM70`L^FfO*c&3E%kz7`!2Qm&6w(u;CZWK6Dap$HyaMn_=vQ<FMP@I`*psb^yXseKy znFF$tvq-N9(t-iSyoM&o53tezVhUb&py&memzkyjHW6eRG@;bgAcPcbk!%LX1K2`j zUC53Dw|q3<20~p2axs$m&@!wRT8JV_Ku9Uz>7t<t$+0j#$eqXq9<)^fQi#{HVE>>N zJ}CYNXFX3y)$8i-25xi}r6#86C_u^_aHMI1GIgRtUcPRA0VvB+vHZfBS`hUPJk_Cx I0aq;-0H1~8LI3~& literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/client.go b/vendor/github.com/lucas-clemente/quic-go/http3/client.go new file mode 100644 index 0000000000000000000000000000000000000000..90115e4b1b82926b8ebfc9edcc466c733c3aae33 GIT binary patch literal 12686 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 X3Pk5LwHTazK}~0H6;fTxRm%kcR&g2e literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go b/vendor/github.com/lucas-clemente/quic-go/http3/error_codes.go new file mode 100644 index 0000000000000000000000000000000000000000..d87eef4ae076a40de382cee10cb5833b0e2a9d8f GIT binary patch literal 2089 zcmXR&OwLYBPgTe$DJd}K;>ye|$S*2U(BR@!O3N)#;^N{|O3y6GC{5B!&d=4)DNRl+ z)=kby%}vcKN!2eb%}mx!&sXBo<l-u+EJ#&IEh@?{a?VdlRRBrpITjS;WF{w;Waj6& z!eqF(lJoP5!8U+Z`{jeB6i@(I25gS4f<c9$fdLmMSjas!FSRH!C!i?5BtJPn2ci=! zgQVCHs@OBHB(*3n5vmGmKDuHfsN&#~qSVA(=b}`EYZZ{Z1965iRIzhTesOAwb5UkV zW^!T<L@S136R2XhqQu<P(7e=&g4E=a)D(z2(4Aq5tQg`gh(ZvFuGkE!*tH@#wYWI5 zEY&AJ5o#``o6Vt$Jzda^Rlrni0aYBFT2hjkmtKsb1>G5zP{qEP#l@L<=`cMY7h{^7 z2vr=ET3DJ|ToRO;g(ozUP!&5T<|U`*;0n5ARK=co$@#ejIjJS7Acvs4IR&cNH?_Dp zF+CN<e;6^C3RUc!pO=@KT!LS58dPyuYEf}!ex6%mPEJx{ayG~j7@=VZOV2KeC5h=p ziMc37qq^B7(a^}u0+hqjO7oHxG>|fxrb2K@QD$DchNeO>n4wV3#aUdQS(2QgkP2ca zCl*7MLvxuG7bj;?YDsBPo`RBxalD^@ylYU9e~=OvCqgN<Jcg#v-PO-E$k8W0Ajm(& z-`U>>izz6X4^6wLUx;gvpCdMHki6uKoDI=52Zsc?I{L;t2e~?ic>4Qcu^L<ML^H(M z$3NKBCEhv6GsM%`(I*~aC`vG(Wm_}@+=3i^UE@RjTq6QpokLt*FtviC6ss=GAV<l_ zXnI^DoLz&1J;Pk%ef%9!?SW=#G!33ESi%IBIkCAa*fk`?)6YE^i#D{Zjb@XtXK=8m zpL;ya0F*#L%jjrYgIohcU4uj7gIv8Z!w;$zmhEw9b$0Y~cJ=W=3wx+uL@9u#+tbh4 z-#5U=HN-U@>IM{7BbN(kI(=P(gB{&nv4j$G`GBU$+27C4)j0%<CbV(_O<$O6P%tQU zyE*#!I5|3dqc{?!)Iihd;uzxS9^~i?w=pF(EwMDG1W8$1Zi!xSK~ZL2Nt%XIX<l|- net90G>{Ce2Pf4{>P*tu_(orbQ%quZ8(@53S<l?O5s^tOz6a$*u literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/frames.go b/vendor/github.com/lucas-clemente/quic-go/http3/frames.go new file mode 100644 index 0000000000000000000000000000000000000000..af9f28eb9f5b1a02e2e01b5db3231d9ee2ae6950 GIT binary patch literal 3740 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go b/vendor/github.com/lucas-clemente/quic-go/http3/gzip_reader.go new file mode 100644 index 0000000000000000000000000000000000000000..01983ac77b8c79eb430d13f89ea40bba3b29a89c GIT binary patch literal 798 zcmXR&OwLYBPgTe$DJd}K;?mbwNX{?FOifWpE6UGR$V)BJFDXjQD=x?{D$z^N2P;Ug z$}9*<O-xBGQYbG<EGSk;R47U<F38U-PE|<CPpMQW&R57RQAkeAQ^-lI%FL+*o0^!D zqX5#W=a*Uz)t#TGP?C|Vkd|3g3{swxqfnBs0FvX%%mrDfpuxqdl$@ViP?TC+tPj$x z#KozUnXkm933f;_NoJN*7Nmk*0J0&qNTIl-s5H4mp_+>m?C8vVJ&=TRPJVG}k%GRy zLTO$~YEe#QW?s5N5Y&5mPWdU7T%1)!3JMBZAoqZ^q!uZF)I<ELo0*qcl9`y3S(Tas zwoRc398_GKRjEZq3aLd!`5@g2V4$zBP+XFkoLvbK=c?u6N-NDvR>(^&cZUbE2Bz~g z6;NGU&Ba-iT2fk+r=SLM704f{Mb#jER^S+^g`2FAUIlSEM2#jmxHSqCqGOXPOHwrz zH1ZTO^Gb9;?g8QaB27>LWu_^lSLuO0plGX*mze_+gjs2z1C!+9tmWc_sx4BmMW_U6 zE`k_p3l0=SBG5>$(gQhElZz9i6%<BDR&auJg2Ze=uHoY3M6xussECUbWMeJd_aN0p h;5gAJ(8TR$a2#uBf{e^B0(k`*cu=S4LFKt>xd4<#1keBg literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/http_stream.go b/vendor/github.com/lucas-clemente/quic-go/http3/http_stream.go new file mode 100644 index 0000000000000000000000000000000000000000..4c69068cdbb5c815822fdcc531f5225b5e26026c GIT binary patch literal 1706 zcmXR&OwLYBPgTe$DJd}K;>ye|$S*2U(BR@!N~$bLEmq><R7%S&QR3p_R7%e*$tX?I zOU}>L&nZn#EY?lVNzF~oD@oNaEX_>TP0v^2(&XaO*H>^<2rel~P0UruELKQV@CXSB z&^J~n1`Fwd<ij&k^AyU9GD|Y^(iKYb6%zAO6pB(4Q$XCbqWoNil8jWSdL4z7#F9ji zL1{&axv41%nRze+T^vIk6~Lm!dR!%y1*tGwL9Wq*xB%>&5V&ay$@zI{`9-<KAX`C} zASqSI%qvMPN=r;m)lo<)Em6qKD=tY*Oi{>B!{yYJ%%arflAKDAm*CD04E1z|yFIf6 ztTz>GAIz7<3UI}spn)oedI1tjAhFWq5`}6mP9zs{adGD7xq*VgwWuh+NC6JgO7oI6 zG`TpzF&31Xo0yrGnV0SfQL9jznO9<F!d1(~RhC$!5DyC_TLm?UW!1G@Tp$w^@>0to zp{7w>Qlx-nv5o?Yy&%OJnwkn)5d9!m7NwSy7UjWBgcxH5@&>A2D-@GzL5>2Mrl3&_ zGfh(=C^a!fBS|4T7UUgG1&z$S5*>w9kdHM%R%NCs6zhSciZOg@YpY-YQc{puRGjLQ zpI>0b#mSkLUj!24<OC-but^G5whCZ1eyJ5D-~iPqh9(#tg}lrhO)gGO&dfBBr3#9+ zAQ6ZOoKSBXfK34@ss$0n<(VbP84772W5Jf_X@HU=*tMLT$%)0O3R)Sdi7BZ?#bC=o zuHxj>*H<Xc&MZ*yaCLNX4GM;)WRQ8B$@zIDnR%tDAPz)3C=(#*#Tft!X?i}XdFdq? zAg!E9MX8C|5TAlV10<G`nwD6aQvz{9u^w`~f>IDC*j=b$19oPyLVj6lkwR%+c3ysY z9>lQ<AYT@POa*x+BqOybH7&m=RY##DBeNKk)lxD`GV}95nKwT#r&0k_R1~GAD3pV< ze=5XbDGG^s`6U^tMGEL9>w!&11bJF+i5@uNr)ek^q^1@r6sP8uC?w`7l;)*Y6r?7X zq^3aJWu>4RqNJmc298Eh4ApXR)^c%if|7q8D4Ro=sYOMg7|t)^0>w41Ahm(!AB~*U zJdGqxNTlY06M%xP0xa3+K@x4W6%J!!HMuxz6;gAGQ^6?-mwu2cE>0X~DCpY4+>)os z1xt$%qp)VA@S@C;R6IE;sT7=K)xeoa#mEw!k+mWxwE4W8VpA*sm<W-&AnYBlws zhL?guIWtWm9ugu}NI?Quq2~muNi;MytwG_9oTCttn_5%^iX@n?;Nc22M3bwQ3jlIU BBk=$L literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/request.go b/vendor/github.com/lucas-clemente/quic-go/http3/request.go new file mode 100644 index 0000000000000000000000000000000000000000..0b9a7278cc0a4442057190304a5fec6f3456465a GIT binary patch literal 2599 zcmXR&OwLYBPgTe$DJd}K;>ye|$S*2U(BR@!N-nA_D9P6^$thOi;#5j4D#|Ycv+`0) z^g*gYq9Ar@Q4WY%TvC*rpH~KDX6B_AD{*mgDy3(ZWRxc9CFker=Oz}Fq~_@sr>5p6 z=H=-Zf~;2J(&XYwE6qz*C`v6XO)V~QE6UIHNKH&hEh^T?fY1ujv0$}&5OKH6)SMJe z1r05bBlLnG+H@4a4%Aer=He_%EK(>)EXmMONGvVM$S=w)snk)(O)bgDPtj2*D9SI% zPtMQLQ7BH%NX<>vQAp0uD@n~O@kz}~FUbflDN-mdDay=C=i=f7Spc!KSi#B`97Iqz zRM&EGrsWqY#Oo+zfTW5N^FUz;b#FBnCuebaW=V2}LWZ7SVs0wPRh-F*#i<HPRv?!t zS#fc4a)MY2wh9?~VTn1Vsa%{e6%eOF)IiwSRKfiQQ3&T^Qw#GnL@A7iO(i5yAnG7& zEUKU(rkeu}GKgZR7$OvrJ(iN1mROoo0`_%gnu216o@a4DacXHwz6LnPI60BSTF)^h zMFSFWI<U~u1O-?v7bj;e7iTRO7pJ~HDBhA2OH%U^OHvh*^YgPaQx%{IMn|DI6_TQh zt@QOv^7C_w^)gdS()99+()BY+a&z>H(vr=L%uJPwQ<F<F^Ye5~^-Q=pGt(4uQu8#B zVm(^P8EmjptfqpUf&n-hk)-v4Q%f{pN_3#<rdZD_KNE*3ItohG3QC%q5I<xVJLl)+ zr6!kvQV=Ynp~=h_oLr&uVDGwCl%(dRq^2l1`}_I1I)|WoG9|Gjv7{(5IXkro;nkG< zWVm-NOiT>n-UWHK7-k^UlGGHCGZ1c4Q-kLf1w~s0B_(hGWn$3=O759y(EOxe3sS34 zQv=R|2p&@U1S<e(;w(xnDJ{xV$ji)u=HFsHztnOKrBpP(S}9mT5~Gd+IR7Xl=A|fD zA&kr|EiO^W%P&z#N>xbBEhwo}0;LyFQc+0FDNa?$OjAI33}ka=8pL^EkAWPEFbm@T zY6X3Lg}nTt+{7GMfP+#XE(b$g1$G9^31qktl=?u0f+i@~3_#8TiNKwPDhx?5P%)67 z@VZS0Ik>=~2hpzpb~Ad=fpQqA6fITIDlN*<3k~vtl&a7&IyA@=n%E)2sYOMQ;+G4W zMxaRwoL@>6Y!%d?rhtk~P&UJria1Nb=|I6&0i+=yv8Xr|TE2${d1`=z1{4dKX&~!B zsWLA!2W$x^dQt)@0~Z&N1ON&j&QiUA#F7jJTX57viW1IJy<l)PpkND4zg(Q0rFtIu z#U%>1i0Xq2WInih0(l)pq84ERQn{++5fT!hZ>(pa1Tv2pPhs&BT7*Jfj1&hjpTMFE zr3_Zc%quYiWpqfXh9VbSQiLebP-I|Eh1TYJ;5hKiE73qv6<kuJqhM&DqhMwN%4o#8 z9pp}^r_?~jD6H}Z#SWyvvr>QpNb1z#;^Yhs@_~ygC@7SI!~#H-I6@Q_@Ng;L#H{=x zu)HxsEHe)(W&jd{)azh1AS*%6fe00YWS#O;D&cYp-~x(^lhYZwrC<fBfDjD_kS<WN zLDB`!xF9)1ItHsi#3D#C#3vXm3UY~pf`VE}PO%;|4`t@(1(zh2q*mAJaB<e^fSkfr z3vDuG<QJFZCFZ7r+DuUMHHuOT6;N76n$WBY$|sp=pk@{*KO<)zXp|JC7J_8Ji62x! M!LpQ+5?3u305JO|%m4rY literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go b/vendor/github.com/lucas-clemente/quic-go/http3/request_writer.go new file mode 100644 index 0000000000000000000000000000000000000000..3dd1cbaef341fb716b2d3e38b998ed82fe479869 GIT binary patch literal 8116 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 Q7vg<r7XwKg?%P@}04ldXtpET3 literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go b/vendor/github.com/lucas-clemente/quic-go/http3/response_writer.go new file mode 100644 index 0000000000000000000000000000000000000000..70a7cd3f484382167a8b7acead1d2cbe237099b3 GIT binary patch literal 2712 zcmXR&OwLYBPgTe$DJd}K;>ye|$S*2U(BR@!N-9mu%vS<2Doav}L5#fA5`B<55WBdf zC^<i`49d*ROD|U9;^I_F&n(F(P0~xw&(+T<O-?M<P0mTpP0cGw)h{f~Ox8^Y8%R)* zer8@tYEfQdj(%xLW)9eHBt5x_MJ1_uy2Yufxrups`h_4bC~;|Wag|gSq$(7p78m5_ z6{m(5WtOBCDHN9!l_r-cRC94A=jY`qKmo`xdd~TId8x@InfZBKoJpl=X{kl2DZwR0 z3R+;F>p=|Q;^NFmO-xBGg6aeZl%5Azh>NqhB(bEl7$%jOSE8V=uK*QH&QD2IC`c?W zPEAoL$yWdy2h#^J5+qcTnx~MIpP$3U#hH_zo}OBy0QRz;517MM%f(feSOkiXc!<qG zsNS<xP($@_buAYsRH<7|X>kU^;W(6fWM(BMXCst>JcCWCCKp#)X<o8IUTQh28#Ibb ziWI;B8eCG8nwYDj01j!e2qK7e6tH+tQ$Y*W^`LkvN-ZfZ%ERy%7bi3#tUxYQfP@Al zJgaMUxH!S_3zbs<I{_q!6t7kaka+b=Ee8dBY7xlcnjl3GmqGPHI67RMwV=QUds(9# z-JO~W5SukLk==ph`f@!;6yr4m><p+u&_t7&SE30rCNoU|W&}tTD3w%madJY!2IMv_ z&dfAeO0!WgG%!%8se$qBY!!?Q3_!+kq8nnXP*PNy3NnDRTn}oeEmR*D7bhsKK^$JJ zkOa#1dQRYs!Nr-Hm#koAs{qbydVZ<pu6dyJU8JD~RjvtE0?9OPnW;G`8W5!r?}0_C z{StFitrV23AoeQhD1;^El%`sN@+hb*(eo_HPt<@&YSwCUadD>Q7b#@xD3pP0E=tTx zPlX2x*mEH1%)FG;3WR(aSOVl)Vw_T3QUofw^g{A|^2<|;G_p0}ZYqlgn;cuK$;HW8 z3kooBApx>U4LM{$`3)WjZbgZ?sntHIdFdq?Rtlw=c_n5h8qfgJ^GVIq(A2Eegg8ti zsWeTKi?du0lHT+@^V0IuG?XA&D<v~8U7<X)Btt<pMM(!5J(}PskJnL1Eh+*zqg)Ru zaU%@Xa{|SihNh-9SfQe=LSAMLI8@+vxfT`W7o}+^CFhssq$uR&mnf8j3QGm3OBK>U z-mp?oEmi_srUzE9p{WT91yHiX?*^zT6uSfB426Qk%AEYf6g<vRL{H?PoKmg_F8(yY zj;@84UDz@*D0`)96evW;f}&MZK_fG-1nlknB3MquVF6etBz<cb85n?V0tJs^QhrLM zV@^(fd1{JVeo-*Ej?jQ*H?X15jI3aw11UdTi;A4`Q!4%POQ8C|L7kEYif6Ty#F9j; zDJv&6PoqFnvle&COi4o|42shar0@Z`Ikl(=>_Dgpwv<?)iMQy3Mh+-lW~PA?s+Fw* zsyR?4q$J}sP>lzwY>UA)5soy2FEv1_Fz2Gw#FG3X4NV0UKFGCjFM}#MkQep!6_WD{ zGE-9&(u(qP!O>|1ZY3G%rRQ_$>nmVO4GKl6pmtWVLU~4N38;=vR7lS(OU;9~UZ8ni z0aVC>3@J!0%FQe(R!9UJr>78{nyL`w=B!|3W@x6PPz-6tD46J(=s{{}Y?eZcRt0b! z18Q@C{8L<>S(2OqVkIXQr^2kYvjtZvYHBb68(RfKOG_(w6{L`sm{Xj}#fhxR7F6w+ U5L9DqfJIF$7c5JF>!(^S04B(HoB#j- literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go b/vendor/github.com/lucas-clemente/quic-go/http3/roundtrip.go new file mode 100644 index 0000000000000000000000000000000000000000..5cde95a62fd8a6ffeee9bc34fbf248f09d1c7b15 GIT binary patch literal 7166 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 V2_Ravp2c7*f=csJHHtvhKLCP*1%Chl literal 0 HcmV?d00001 diff --git a/vendor/github.com/lucas-clemente/quic-go/http3/server.go b/vendor/github.com/lucas-clemente/quic-go/http3/server.go new file mode 100644 index 0000000000000000000000000000000000000000..086bc8e256452aa23187438c9785ef36bd5b33cc GIT binary patch literal 22266 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 CvcrP_ literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/.codecov.yml b/vendor/github.com/marten-seemann/qpack/.codecov.yml new file mode 100644 index 0000000000000000000000000000000000000000..00064af33101737796f3746de1a6b8059bab0b3f GIT binary patch literal 103 zcmYe!FH0>-Oi#7qQcx($FU?D_Qpih9EJ`gd;Zjg2E=epYEe1&`C=?XsXQd{WKsX8t sDXD3Rr8#h}f<j3~QEG8Ueol&&f+3fJf`URpVo7p_l|ou#PH`$10CGhhd;kCd literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/.gitignore b/vendor/github.com/marten-seemann/qpack/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..66c189a0904978caf9f02955729eb557c2856656 GIT binary patch literal 110 zcmYc*t*Xk*OV`)ZtI90kg7K2`%TkL9it^Jkb5aq4MTx~3sYS(bf#UqUL=08Mr3D2= Usl~;a`FRMH$@xVErN#PO081At*Z=?k literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/.gitmodules b/vendor/github.com/marten-seemann/qpack/.gitmodules new file mode 100644 index 0000000000000000000000000000000000000000..5ac16f084afde78701f1ae3b0c1c963ff2150fca GIT binary patch literal 126 zcmazpE=|hKPbtkwRZz;zD@jc+N-W9D&nrnSE-BUr2^8fQ=oe<D6)VMZaTX+&WGL7w zfP`_X<KiqW%2BXY$S5f(D7MnqPtPpLC{5B!&d=2^EJ#ewPAw`1o2QqaS;7SXzaB1u literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/.golangci.yml b/vendor/github.com/marten-seemann/qpack/.golangci.yml new file mode 100644 index 0000000000000000000000000000000000000000..4a91adc77ace30b4359b17c42ff01ef5bd2d2e20 GIT binary patch literal 447 zcmXRa&9mal$;>NBEh^S6PAw_P%u6qZi&$|fD5PW-Cnn{j>L%voSSgegm8NnjD5U0r zgg~+i3c3o3#mSkO$r-81*<1<=Ac2(B#FXUx6p%^~H?<-ov9!1(vkW0nkY7}ilb>Hu zl$r+Blb)ZPpI2N0=cVPAC@3owrKXhTr6lH+=qMCt<|U^pr00Vqw2CwGOLJ1R6p~UE z5*70Db@K}p64Odji$L<FxdlkZpztztK^7OoEh)~-Ey#hnDl;!NEiJLQI5RyDsvtMB zxVRuSCkM(cC`wJt$;nTKa*LDm3sONr2jiC{mSiR)heL5mWlk!pKv8LO39?9OUUGh3 kS!xl?#ie-#iA9OIP$Nt8N{drdpq#SAB4nLqsU=)o0BfFxWdHyG literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/LICENSE.md b/vendor/github.com/marten-seemann/qpack/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..1ac5a2d9ae6a99a7da8cf29193770a0d8c04e430 GIT binary patch literal 1054 zcmZ?HFQ_caOwTA$FfuT-RPaqKDoM>#2u@ASP0Y*V;tEJD%FQe;&dkqK$ShXKNG(cD zs#HiXO3W)sP0>+ED@sjO$WK#9&PXguPt{Q<$yZ3st5hgREh^5>Q^-#$NzBa4%u82D zR7lP*s067j$;d2LD9%qSDNig)RY=TBQAjK<&QH!vEJ;mKNXbtw%}vcKNh|@`mzJ56 zTCAW^l98&Q6b#j&qzSepB{eZeAu~?_BnOjHD9<d($S*BXC`v6ZDauR+nW3YQnU|bX zngVhIOgbksHxp_DNITf?#UPiL7N_cf-KnFHo1c=IR;i<qn+o<;L1|J>W^sm&LP{pc z=%mt;R2_w4kYI9Z9!OVWUW$HxkwS55P7cU~%+z9tUsDxeuG0bgwIm-DHo2K4AgVY& z4{UFFMt&};Pcn-Y(n^c+GK(`(Q$Q|D$yX@O2iusHnp^@B0l7RaKPM-@9OSj+{JfM* zkbjG<xVS<xQWX-D^2<^|E`dgEUVcetG9<Xc5m11LRj5>PMq*BmLQ*O;z*18{!3z>f z&MyFarwHWX;*!L?lFY;$g@XJduzl#>)Z^j`@o-fL_IC>jcMNh>@C;T62=Wi}ba8c2 zP;v}b@C;VcQ3&@8@$e4~Q3!Vo3Uc%diB#}+Q*iW)RPgrnbJ0<7jR**G4GvcD4^r^- z4e;@Fb<t7q^mFzJb@B9bS8xgqQSkE*QSkBf^$c-!Q3&x@09gn%%F{I%WSFmOkh6!Q zUx=fVr;leyq>h4{XNVujOgH}^1xJMd$Dj~T=TINVAccU?paB11R|Q8u7X?3mKTkim zAWuJcS6^4Z5IqG?KLtO31=lcFzYv9B4@Vy#uvLzsAs+rgVD~xu2Sf&Wx_g8uc=-Fc zxCR9)IJqkLcse@yxGI3H@{3e(_Hp#|)lqP9^mTLxxh_b-Kg7c|2&@e1x^NFyu#l&p zf}@{;qjQL-zaPjO&i;NOL5|KLItn5FK_Q?30c#ES40hE~a18Pc1_g*)kiV~v0w_5B z-9Sn_{S=)2{rp@ZrhtMP>}q(7_y>Xcp~0>o)nEe^TwEP}JpJ5*K|ThBCQPv&7XbS% BQHcNm literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/README.md b/vendor/github.com/marten-seemann/qpack/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a621825ac9d5b5d7f5162d7a62c739a63f1a1b56 GIT binary patch literal 1203 zcmY#Z2n=v^_U7V>R*ZJfPsvYK2ue*$ElSNxPL0*bC@Co@w$j(n%uUxT&d5y7Nh#LL z%-2s!Oi54GPY0>iErO}mP0A@v)hjMbw=XWK%t^IP%SkNJEiNogEK1cxm;~0OmtT~w zpPpHgQJSQeoS&<on^;tmnx|Wwnwp!Km#1G?keHmU338BgW>IoZs<WqpQ)y;Sib8Nn zVo7N+!Z0*vC4*HbXX+Oe<!7ZPmq4w+YCLv_s^ugmrRLbetkq5eS&QU)m<?c8r)OX_ z8RGE#lvD-h{Ib-d#B|)@n4F)Inw(#zpN!9?`nie4C8<T&9gOTYh+&}k!Ra!tkc`Y? zh0J1wM1|bUyv*Fh97yadWabv+q~@mPl_ZvA=I1G7<|(-6>nV7afYicNX67lBWTYw- zr{)!>DwJd-mMCPFD5T`47U!v#D3pT5QWa7v^AdA2lNCx5lX6lO5=#^kb8>VPl1fVy zvPz3fAO?Uefvd<%QPA)xO-sv7%u`6s1BFP6rb2N^QD$DcLQZB$YEfcNv7SPBMru)N zS!$6&L4I*@W>QY74#@JN)Y9VAVg-;Ug|wplT(I%c9-#D$#YZ5=yXPwumn7z;Bo?J8 z<YXolB^Fg8k`q#jOwZ3r%uCnLOD)kaDN0R+#(_R4KN#s}fYYZQ7nf&=LUBfZX-<kl zQmO*j!zK9&nRz9tMfnA(MTsS;3gww484CF&8L3660SyX*{JdfXjimgN45+`0iWL&` zQWQW*A|<t`SW`zKBflK%>C6&^@_bMzCnjfPrk15DBr0TNre`Q5=jRp_r4|>1qAWEn zEi*YYH7~hRkBduLSs|!2FAo%kAYVhBq)?JtTvE)%<(XGpl9-bN2`zB$hdEjyEi)$- zDT5(}2sl%w78UCkW~LSEYbqpFDilGi;7UkHNJ=cu;7ZRdQ7A4=%FRzH%}G@#El2@{ zfUa(4US^4cu5M9ka%oX<W?3poJr`F<Mrt0&1h6Z>4zmJDKn=>vPS01+EdskMy$GBr J!D>KCxBz6%uW$eW literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/decoder.go b/vendor/github.com/marten-seemann/qpack/decoder.go new file mode 100644 index 0000000000000000000000000000000000000000..c9001941333ebe646960641122a87fb408179fb0 GIT binary patch literal 6190 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/encoder.go b/vendor/github.com/marten-seemann/qpack/encoder.go new file mode 100644 index 0000000000000000000000000000000000000000..13e0ad2c7d1f9631e8fef48c655b4da24acc4a08 GIT binary patch literal 2616 zcmXR&OwLYBPgN)cF}S!ga|`l|N)$A>IF&N<mAJS#mD2Nb67$ma@{7{-EA;bHOY}2J zN(zkhGeGK<xHP%A^z{`S^Aue3lJiqiixdh{i_-Foa*GuL100>b6;eUMnR)4YTqTtS zsc==rB}JvlB?{GCoaIINC8+^LscD%N3Q76-Ib2+v<q8T4nfZF*MVTe3MO>UorD+P$ zu}PIBsa&;S+x$|?VYU^emXsFd6)Pkv<fWFwO)t;LOwPdR5(URRkek6SOHENoNi0cJ z$ShVUFUl+_NzGFz$yX@X<4P;dOIAQwq*0D=k*0zc%=&6BPKXN>)SyDu<yH#iwNQtG zec+avnv((vhty()G=<E(l6-|kh2qS-^qf=$uo94+sp`cFrFki-MLCt3dFc@GB0Z2p zLozap6+rGO$;{7F$W5$NNX#kDS12gTPbp1KRR9G{u|isYkwQsEs)9#qA}AJ|a`Kb2 z6(GS1G9xohAulyKwYWI3s8UbCGYzIYMMojC1myda{Jd0!q|~(hqEv`mL5@g+hJ!{b z+{>B}mw`Q{kp{I6%+pjzEh@?{0>!DmJ~%W}ak_{zGfhD;RS!AttGPHiQ}sY`ZL5%2 zP>`CJ5|&uxnOC9#7SvI&&`~hZB%sC|t_Im;TZNLM(o`<aS}rcm%#;d8kjXj4ItqD- zxv6gXrFkg|R<;m7rxy7p7DT7%`6cG2#&UriiBJP_UuK#D!T`Op#GKO9Vg*}Ug}lrh zkheKOI&xC;G}83KKysQOIRl6cC&;&%C8?fyDXA5yDc}&(Kr$>dr2^zT&RT`koZ?he zJwBj(keCD36P{U;0dihYYFcVhYF=`xhFY2qva8?*adDy;R+gCKnNp#n0CqLp+ej_| zOB6>#-5$%u3HA<H6rvmy0-!hoWfW9@K+L9r`?-+401ZL((8w<>K@E^JO-Nj)>Xk#v z5J(cz<btO!aFU9)GKhs|-@MEmXr6V>$uCY-NJ&l3Ni0e&Rw&6xECDBDaB%_36q&^e z$@#ejIjJS73W<3s3Pq{KsU;wV@X`&G;Y#uql2R3lQcH_dQxp=@6EpJ^Kv@)0PGvwc zX%Z-tV#{J+H)tSbD3I3?#iXr5T4GLdD!SuAMT7!MRt3it;qXGu#noJ#`DtmzsU@In z0ZPr_&_W75Y`N1|M*&<R=y{Z;rR65(K}tiP)V%bP3=K$z(u5fi4KY4eA<kC8pu)&N zAx^=d!U7RH5Q7}SWdPK;;F6-uymUxB>L@_XgS*pKfmmmn<98+`*J5|4g#k$}gct=) zQ;^UA)l$%yPXpHp<r$!)1}b7eC2#>KA?1}QWacS=%HsSqaIz~&Ov*_u#+FF1MlM=T zP|$+Z2jHRuR7@z8X6BVxXoAWYNGy;N$tF5r?Pew#nJJ*y)Yn%CPKD$S14F-%ii(N~ zg@VMAlGLI+9Z+gd24xpNP~~O-&M6@vrXe&KQB#4b!4QniJcUe1X|4c{PaTEa)WkfH zpEFC;ixpDyN-~R5b1D^5QqwZ?Qd3BaShRWq9Ps4En;GtSGX%#Pws^x6S;+=mwOjyy C0#`Z! literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/header_field.go b/vendor/github.com/marten-seemann/qpack/header_field.go new file mode 100644 index 0000000000000000000000000000000000000000..4c043a9928c5a45d38de71bdb83acd9f7687ec9d GIT binary patch literal 477 zcmXR&OwLYBPgN)cF}S$&^%WcyJW>-=Qj6R&Q*%-jGK&=w74i~uQ+3M{b4pVc3KBDm z^c0-(OEMHnGE%`Z3W<3s3J}@EqEwI(B}J);C8;S2iNy-}1&M{FsS3rZg{7%^$*IK( z`DqII$t9^J#d=&Nl?ACNmKB#2l_r-cRC96qCFZ6oC={0zW#*-GafX4dhVp8`uJkMp zC{8U+$yX>!Eyyn_DOM=YNG-`oEdskL1MC5XG`J7)6g)yg0*n<3AbKG3dLXBUWF(d- zWESfvWR_qUm06-tT#^VjJhLQ2AyFYYKPNvAtj!aoNg*%4L?It!Yk6jIszQ2cVo_pV zNos0}LP@?tQmR5CC{!|2P;G^{N(W?jNk)EYdWIfXT4`Rgf<{Ifa#(08z<j8osgRVP xp92b&qSTVoqCAD1)I5!hG(AulX(}k%Di|oJsVP7Oq77mdY;6_Pt<<?{xd7QBpJf05 literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/static_table.go b/vendor/github.com/marten-seemann/qpack/static_table.go new file mode 100644 index 0000000000000000000000000000000000000000..930e83c7c12c392c5d3c882b2a0231bcd021bfa6 GIT binary patch literal 9113 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/marten-seemann/qpack/varint.go b/vendor/github.com/marten-seemann/qpack/varint.go new file mode 100644 index 0000000000000000000000000000000000000000..28d71122e139c922be0232e48d26da18432366ee GIT binary patch literal 1589 zcmXR&OwLYBPgN)cF}S$&^%auy3o=tv6w-?Fa}`Q5QWf0u6^cs|^HLIvQWSDBlZq0H zDiu5e9G$%tGII-ZQgc)DN)k&l^Ygg4GII;^i%JxfQj3c6i;9)FxXKcX6jF<d!V-%z z^Gf{7Qj5}Z^2-%$L1Otu#d?0J<r+$5U`d6{ypq)P)FOp^s468*uwxPn3R3e@!V-%- z^GXyToMMGc9fibVh19&{{FKxbh0Ht!kQs?dIjL|{((;RP6-tXU^U@XaKz>TfEKw*Z zN=?hG&`~JKS4b%?QAo^7Q7B3+DJ{w?275ELq9ipBWL{EfT3Tw69+$p8NM~?rDu~J` zDJihh*9TEXdg+-Z8Kp^jnfVaDZgD|svVL-YZb4CMadBpTo?b>tZjLh4M|wr6AaQso zrIqF-Lwy4AmIlZ*(XmODC8;_Jc?u9ZQ=v37uf)tmQvo8QP|d}etzcyf71q!&RIss8 z$kSBNRWQ`#;>=7_$W*XV$Ob9ng!l?-2gr0Cu%Q~6nwngkwOpJa`)o0#vq93C3c9um z*<74y`9%uW3YiLawhD$u7GRd0t%5n&d>r}>Dl80YG&0o;D$LU~L1xu*;dM$aID(5( z6JgN{ViqeT<|&ls6=$aBrKTX_H77MMy(9x3z4>Wrpg03XOj2rkW?mjBqVv-f3iK56 z6fz<4S&&$in44OXS_DcfDXGQDMVU$9)B@54iboQovnVZDuQ)Xsl+yJ~^$cO@BTpf> z6qK5B$`dP#6_QdFl2S{`Q&aO448bYP0;Vn`BNdw5QbFl1H!(9$0g}={4o=N1$pE=j zp*S}&CnvQ?p|~_HEwe%a<R=}4d~hyOP_$LZ%gg~8r~onzoXfzPi;DbGQ&W8Ni&7Ob z(-aC6Qu0%a^VCZelJoOQK=vdmfD&g;Y6(1p=y8FQAt=P5i4d0X3ZMy2Q$Yik26YsQ zQo-(pBt;!i_66bmB27?w$V^knQ?OAmRH&&@$WyRWumA~i79{3nCTl1qC8j9kDM8X* zW|~4yYMw@crh=`lf&o}n5jX=X80aVzfQ^Ou1DsAX72wIeAle`nlFUI?L2SrWP_tEl z<O4`v(a6&T=Ld*pNRH4jw6Q_R>KY>3nyI5u5N&7`s{;yFkR~q9{G!bC08ln7;NmP$ zu!YETae~URTxiyV<PE4t?4bTg0_%x3h~?r$*2kHtpbhs!lA58Bxh5#v=5ld@ax6%| z0udIl%mQ*2)JdS+%9#r?!yKX|7nC8*j1{UC^z{`&{9XL56bg#+3y{i9h2)IX<ZL~K w+{A29rYOnCELJEj(BtCdgqd#uN>G{U0pI{fEt<JFK{g`tEs9cj+|+Ub0A>j7Z~y=R literal 0 HcmV?d00001 diff --git a/vendor/modules.txt b/vendor/modules.txt index cf5e87e03dec0cb446390ef3e2043fa1f337cbe7..170d03040e5282e5a3f9ca4ddfd1e9471e212b05 100644 GIT binary patch delta 61 zcmZ2yw8ePCDOT2ul9B@B$@OeHn=iArF>x0pCTA;@8R!}58BQ)>6=5`)T+boH4ie>3 PR+!wtp}IMl<Fo((i&ztQ delta 21 dcmdmDyv}IDDb~sV**Q1=W^H5Ie4pd0003?d38Mf2 -- GitLab