Skip to content

Commit

Permalink
Enabling 0-RTT for QUIC/H3 clients (#387)
Browse files Browse the repository at this point in the history
* 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

* changed doq client to use DialEarly for 0-RTT support

* Added 0-RTT toggle - introduced the Use0RTT option to the configuration

* reverted the doqcliet DialEarly changes as they were not needed and had a bug.
Renamed the 0RTT toggle and updated the documentation.

* catching missconfig of 0-RTT and HTTP/2
  • Loading branch information
LeonardWalter authored May 9, 2024
1 parent 9ceabec commit f2a08d6
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 1 deletion.
3 changes: 3 additions & 0 deletions cmd/routedns/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 `toml:"enable-0rtt"`
}

// DoH-specific resolver options
Expand Down
3 changes: 3 additions & 0 deletions cmd/routedns/example-config/doh-quic-client.toml
Original file line number Diff line number Diff line change
@@ -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"
enable-0rtt = true

[listeners.local-udp]
address = "127.0.0.1:53"
Expand Down
2 changes: 2 additions & 0 deletions cmd/routedns/example-config/doq-client.toml
Original file line number Diff line number Diff line change
@@ -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"
enable-0rtt = true

[listeners.local-udp]
address = "127.0.0.1:53"
Expand Down
2 changes: 2 additions & 0 deletions cmd/routedns/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 4 additions & 0 deletions doc/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,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:

Expand Down Expand Up @@ -1539,6 +1540,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)
Expand All @@ -1564,6 +1566,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:

Expand All @@ -1573,6 +1576,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)
Expand Down
15 changes: 14 additions & 1 deletion dohclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -181,7 +186,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.Use0RTT && 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
Expand Down Expand Up @@ -268,6 +278,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 {
Expand Down
7 changes: 7 additions & 0 deletions doqclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ type DoQClientOptions struct {
TLSConfig *tls.Config

QueryTimeout time.Duration

Use0RTT bool
}

var _ Resolver = &DoQClient{}
Expand Down Expand Up @@ -77,6 +79,11 @@ 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
if opt.Use0RTT {
tlsConfig.ClientSessionCache = tls.NewLRUClientSessionCache(100)
}

if opt.QueryTimeout == 0 {
opt.QueryTimeout = defaultQueryTimeout
}
Expand Down

0 comments on commit f2a08d6

Please sign in to comment.