From 597d78eb6dcd9c038cdbe88821b69c3f818f9459 Mon Sep 17 00:00:00 2001 From: LeonardWalter Date: Mon, 6 May 2024 12:54:02 +0300 Subject: [PATCH 1/5] Updated the DoQ and DoH QUIC client to enable 0-RTT based on the guide from: https://quic-go.net/docs/http3/client/#using-0-rtt --- dohclient.go | 10 +++++++++- doqclient.go | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/dohclient.go b/dohclient.go index 091e16ff..a56a4960 100644 --- a/dohclient.go +++ b/dohclient.go @@ -181,7 +181,12 @@ func (d *DoHClient) ResolveGET(q *dns.Msg) (*dns.Msg, error) { ctx, cancel := context.WithTimeout(context.Background(), d.opt.QueryTimeout) defer cancel() - req, err := http.NewRequestWithContext(ctx, "GET", u, nil) + method := http.MethodGet + if d.opt.Transport == "quic" { + method = http3.MethodGet0RTT + } + + req, err := http.NewRequestWithContext(ctx, method, u, nil) if err != nil { d.metrics.err.Add("http", 1) return nil, err @@ -268,6 +273,9 @@ func dohQuicTransport(endpoint string, opt DoHClientOptions) (http.RoundTripper, if err != nil { return nil, err } + + // enable TLS session caching for session resumption and 0-RTT + tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100) tlsConfig.ServerName = u.Hostname() lAddr := net.IPv4zero if opt.LocalAddr != nil { diff --git a/doqclient.go b/doqclient.go index 8c0a4102..5108b141 100644 --- a/doqclient.go +++ b/doqclient.go @@ -77,6 +77,9 @@ func NewDoQClient(id, endpoint string, opt DoQClientOptions) (*DoQClient, error) // quic-go requires the ServerName be set explicitly tlsConfig.ServerName = host + // enable TLS session caching for session resumption and 0-RTT + tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100) + if opt.QueryTimeout == 0 { opt.QueryTimeout = defaultQueryTimeout } From 4d4f1a18aaaf3c6bd6d3e5543c902621aa6adf5a Mon Sep 17 00:00:00 2001 From: LeonardWalter Date: Tue, 7 May 2024 13:21:13 +0300 Subject: [PATCH 2/5] changed doq client to use DialEarly for 0-RTT support --- doqclient.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doqclient.go b/doqclient.go index 5108b141..4f8564f7 100644 --- a/doqclient.go +++ b/doqclient.go @@ -212,7 +212,21 @@ func (s *quicConnection) getStream(endpoint string, log *logrus.Entry) (quic.Str // If we don't have a connection yet, make one if s.EarlyConnection == nil { var err error - s.EarlyConnection, s.udpConn, err = quicDial(context.TODO(), s.hostname, endpoint, s.lAddr, s.tlsConfig, s.config) + s.udpConn, err = net.ListenUDP("udp", nil) + if err != nil { + log.WithError(err).Debug("couldn't create UDP connection") + return nil, err + } + + // Resolve the UDP address for the endpoint + udpAddr, err := net.ResolveUDPAddr("udp", endpoint) + if err != nil { + log.WithError(err).Debug("couldn't resolve UDP address for endpoint [" + endpoint + "]") + return nil, err + } + + // Use quic.DialEarly to attempt to use 0-RTT DNS queries for lower latency + s.EarlyConnection, err = quic.DialEarly(context.TODO(), s.udpConn, udpAddr, s.tlsConfig, s.config) if err != nil { log.WithFields(logrus.Fields{ "hostname": s.hostname, From fe20e75e0ad3d6a568ce5df712637328a26a4487 Mon Sep 17 00:00:00 2001 From: LeonardWalter Date: Wed, 8 May 2024 02:20:55 +0300 Subject: [PATCH 3/5] Added 0-RTT toggle - introduced the Use0RTT option to the configuration --- cmd/routedns/config.go | 3 +++ cmd/routedns/example-config/doh-quic-client.toml | 3 +++ cmd/routedns/example-config/doq-client.toml | 2 ++ cmd/routedns/resolver.go | 2 ++ dohclient.go | 7 ++++++- doqclient.go | 6 +++++- 6 files changed, 21 insertions(+), 2 deletions(-) diff --git a/cmd/routedns/config.go b/cmd/routedns/config.go index 6e68dd8a..6c63d976 100644 --- a/cmd/routedns/config.go +++ b/cmd/routedns/config.go @@ -57,6 +57,9 @@ type resolver struct { Socks5Username string `toml:"socks5-username"` Socks5Password string `toml:"socks5-password"` Socks5ResolveLocal bool `toml:"socks5-resolve-local"` // Resolve DNS server address locally (i.e. bootstrap-resolver), not on the SOCK5 proxy + + //QUIC and DoH/3 configuration + Use0RTT bool } // DoH-specific resolver options diff --git a/cmd/routedns/example-config/doh-quic-client.toml b/cmd/routedns/example-config/doh-quic-client.toml index d0367666..d5cc7ff0 100644 --- a/cmd/routedns/example-config/doh-quic-client.toml +++ b/cmd/routedns/example-config/doh-quic-client.toml @@ -1,9 +1,12 @@ # DNS-over-HTTPS using the QUIC protocol. +# New connections get initiated with 0-RTT if possible. +# Use0RTT will overwrite the method to GET. [resolvers.cloudflare-doh-quic] address = "https://cloudflare-dns.com/dns-query" protocol = "doh" transport = "quic" +Use0RTT = true [listeners.local-udp] address = "127.0.0.1:53" diff --git a/cmd/routedns/example-config/doq-client.toml b/cmd/routedns/example-config/doq-client.toml index 1b2ca09d..81e6ce41 100644 --- a/cmd/routedns/example-config/doq-client.toml +++ b/cmd/routedns/example-config/doq-client.toml @@ -1,11 +1,13 @@ # This config starts a UDP and a TCP resolver on the loopback interface for plain DNS. # All queries are forwarded to a local DNS-over-QUIC server. +# New connections get initiated with 0-RTT if possible. [resolvers.local-doq] address = "server.acme.test:8853" protocol = "doq" ca = "example-config/server.crt" bootstrap-address = "127.0.0.1" +Use0RTT = true [listeners.local-udp] address = "127.0.0.1:53" diff --git a/cmd/routedns/resolver.go b/cmd/routedns/resolver.go index b7588968..80d56b58 100644 --- a/cmd/routedns/resolver.go +++ b/cmd/routedns/resolver.go @@ -25,6 +25,7 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv LocalAddr: net.ParseIP(r.LocalAddr), TLSConfig: tlsConfig, QueryTimeout: time.Duration(r.QueryTimeout) * time.Second, + Use0RTT: r.Use0RTT, } resolvers[id], err = rdns.NewDoQClient(id, r.Address, opt) if err != nil { @@ -81,6 +82,7 @@ func instantiateResolver(id string, r resolver, resolvers map[string]rdns.Resolv LocalAddr: net.ParseIP(r.LocalAddr), QueryTimeout: time.Duration(r.QueryTimeout) * time.Second, Dialer: socks5DialerFromConfig(r), + Use0RTT: r.Use0RTT, } resolvers[id], err = rdns.NewDoHClient(id, r.Address, opt) if err != nil { diff --git a/dohclient.go b/dohclient.go index a56a4960..8e296070 100644 --- a/dohclient.go +++ b/dohclient.go @@ -44,6 +44,8 @@ type DoHClientOptions struct { // Optional dialer, e.g. proxy Dialer Dialer + + Use0RTT bool } // DoHClient is a DNS-over-HTTP resolver with support fot HTTP/2. @@ -85,6 +87,9 @@ func NewDoHClient(id, endpoint string, opt DoHClientOptions) (*DoHClient, error) if opt.Method == "" { opt.Method = "POST" } + if opt.Use0RTT && opt.Transport == "quic" { + opt.Method = "GET" + } if opt.Method != "POST" && opt.Method != "GET" { return nil, fmt.Errorf("unsupported method '%s'", opt.Method) } @@ -182,7 +187,7 @@ func (d *DoHClient) ResolveGET(q *dns.Msg) (*dns.Msg, error) { defer cancel() method := http.MethodGet - if d.opt.Transport == "quic" { + if d.opt.Use0RTT { method = http3.MethodGet0RTT } diff --git a/doqclient.go b/doqclient.go index 4f8564f7..41c9b641 100644 --- a/doqclient.go +++ b/doqclient.go @@ -42,6 +42,8 @@ type DoQClientOptions struct { TLSConfig *tls.Config QueryTimeout time.Duration + + Use0RTT bool } var _ Resolver = &DoQClient{} @@ -78,7 +80,9 @@ func NewDoQClient(id, endpoint string, opt DoQClientOptions) (*DoQClient, error) tlsConfig.ServerName = host // enable TLS session caching for session resumption and 0-RTT - tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100) + if opt.Use0RTT { + tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100) + } if opt.QueryTimeout == 0 { opt.QueryTimeout = defaultQueryTimeout From 9934842e36b43579b6004463d1270f3f4a4b994b Mon Sep 17 00:00:00 2001 From: LeonardWalter Date: Wed, 8 May 2024 13:16:23 +0300 Subject: [PATCH 4/5] reverted the doqcliet DialEarly changes as they were not needed and had a bug. Renamed the 0RTT toggle and updated the documentation. --- cmd/routedns/config.go | 2 +- cmd/routedns/example-config/doh-quic-client.toml | 2 +- cmd/routedns/example-config/doq-client.toml | 2 +- doc/configuration.md | 4 ++++ doqclient.go | 16 +--------------- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/cmd/routedns/config.go b/cmd/routedns/config.go index 6c63d976..577d621c 100644 --- a/cmd/routedns/config.go +++ b/cmd/routedns/config.go @@ -59,7 +59,7 @@ type resolver struct { Socks5ResolveLocal bool `toml:"socks5-resolve-local"` // Resolve DNS server address locally (i.e. bootstrap-resolver), not on the SOCK5 proxy //QUIC and DoH/3 configuration - Use0RTT bool + Use0RTT bool `toml:"enable-0rtt"` } // DoH-specific resolver options diff --git a/cmd/routedns/example-config/doh-quic-client.toml b/cmd/routedns/example-config/doh-quic-client.toml index d5cc7ff0..e7d55773 100644 --- a/cmd/routedns/example-config/doh-quic-client.toml +++ b/cmd/routedns/example-config/doh-quic-client.toml @@ -6,7 +6,7 @@ address = "https://cloudflare-dns.com/dns-query" protocol = "doh" transport = "quic" -Use0RTT = true +enable-0rtt = true [listeners.local-udp] address = "127.0.0.1:53" diff --git a/cmd/routedns/example-config/doq-client.toml b/cmd/routedns/example-config/doq-client.toml index 81e6ce41..be92eda4 100644 --- a/cmd/routedns/example-config/doq-client.toml +++ b/cmd/routedns/example-config/doq-client.toml @@ -7,7 +7,7 @@ address = "server.acme.test:8853" protocol = "doq" ca = "example-config/server.crt" bootstrap-address = "127.0.0.1" -Use0RTT = true +enable-0rtt = true [listeners.local-udp] address = "127.0.0.1:53" diff --git a/doc/configuration.md b/doc/configuration.md index e58596d4..f4194f2f 100644 --- a/doc/configuration.md +++ b/doc/configuration.md @@ -1508,6 +1508,7 @@ Example config files: [well-known.toml](../cmd/routedns/example-config/well-know ### DNS-over-HTTPS Resolver DNS resolvers using the HTTPS protocol are configured with `protocol = "doh"`. By default, DoH uses TCP as transport, but it can also be run over QUIC (UDP) by providing the option `transport = "quic"`. DoH supports two HTTP methods, GET and POST. By default RouteDNS uses the POST method, but can be configured to use GET as well using the option `doh = { method = "GET" }`. +DoH with QUIC supports 0-RTT. The DoH resolver will try to use 0-RTT connection establishment if `transport = "quic"` and `enable-0rtt = true` are configured. When 0-RTT is enabled, the resolver will disregard the configured method and always use GET instead. Examples: @@ -1535,6 +1536,7 @@ DoH resolver using QUIC transport. address = "https://cloudflare-dns.com/dns-query" protocol = "doh" transport = "quic" +enable-0rtt = true ``` Example config files: [well-known.toml](../cmd/routedns/example-config/well-known.toml), [simple-doh.toml](../cmd/routedns/example-config/simple-doh.toml), [mutual-tls-doh-client.toml](../cmd/routedns/example-config/mutual-tls-doh-client.toml) @@ -1560,6 +1562,7 @@ Example config files: [dtls-client.toml](../cmd/routedns/example-config/dtls-cli ### DNS-over-QUIC Resolver Similar to DoT, but uses a QUIC connection as transport as per [RFC9250](https://datatracker.ietf.org/doc/rfc9250/). Configured with `protocol = "doq"`. Note that this is different from DoH over QUIC. See [DNS-over-HTTPS](#DNS-over-HTTPS-Resolver) for how to configure this. +The DoQ resolver will try to use 0-RTT connection establishment if `enable-0rtt = true` is configured. Examples: @@ -1569,6 +1572,7 @@ address = "server.acme.test:8853" protocol = "doq" ca = "example-config/server.crt" bootstrap-address = "127.0.0.1" +enable-0rtt = true ``` Example config files: [doq-client.toml](../cmd/routedns/example-config/doq-client.toml) diff --git a/doqclient.go b/doqclient.go index 41c9b641..b90bdbee 100644 --- a/doqclient.go +++ b/doqclient.go @@ -216,21 +216,7 @@ func (s *quicConnection) getStream(endpoint string, log *logrus.Entry) (quic.Str // If we don't have a connection yet, make one if s.EarlyConnection == nil { var err error - s.udpConn, err = net.ListenUDP("udp", nil) - if err != nil { - log.WithError(err).Debug("couldn't create UDP connection") - return nil, err - } - - // Resolve the UDP address for the endpoint - udpAddr, err := net.ResolveUDPAddr("udp", endpoint) - if err != nil { - log.WithError(err).Debug("couldn't resolve UDP address for endpoint [" + endpoint + "]") - return nil, err - } - - // Use quic.DialEarly to attempt to use 0-RTT DNS queries for lower latency - s.EarlyConnection, err = quic.DialEarly(context.TODO(), s.udpConn, udpAddr, s.tlsConfig, s.config) + s.EarlyConnection, s.udpConn, err = quicDial(context.TODO(), s.hostname, endpoint, s.lAddr, s.tlsConfig, s.config) if err != nil { log.WithFields(logrus.Fields{ "hostname": s.hostname, From bfbf1170c5d339a86836ed15f24e033ba868d320 Mon Sep 17 00:00:00 2001 From: LeonardWalter Date: Wed, 8 May 2024 15:09:00 +0300 Subject: [PATCH 5/5] catching missconfig of 0-RTT and HTTP/2 --- dohclient.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dohclient.go b/dohclient.go index 8e296070..9d30d3d2 100644 --- a/dohclient.go +++ b/dohclient.go @@ -187,7 +187,7 @@ func (d *DoHClient) ResolveGET(q *dns.Msg) (*dns.Msg, error) { defer cancel() method := http.MethodGet - if d.opt.Use0RTT { + if d.opt.Use0RTT && d.opt.Transport == "quic" { method = http3.MethodGet0RTT }