From f3157b0a42d5d649d5430fec699b1dd2a4721aeb Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 5 Aug 2020 14:39:09 +0200 Subject: [PATCH] Check DoH servers with a query to a random name The issue with benchmarking DoH servers is that some responses can be directly served by a CDN, while others require a round trip to the origin that can be significantly more expensive. Random padding was an attempt at mitigating this. Unfortunately, some servers (Tencent) ignore the padding. We end up with a query for the root zone served by the Tencent CDN very quickly, but anything else is orders of magnitude slower. So, measure a query within the reserved "test." zone instead. Caching resolvers should either know that "test." is undelegated, or have it in their negative cache already, so this is unlikely to trigger an actual query to authoritative servers. Take it as an opportunity to check that we don't get anything but a NXDOMAIN response for nonexistent domains. --- dnscrypt-proxy/serversInfo.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dnscrypt-proxy/serversInfo.go b/dnscrypt-proxy/serversInfo.go index 64f39cbb..f0637be8 100644 --- a/dnscrypt-proxy/serversInfo.go +++ b/dnscrypt-proxy/serversInfo.go @@ -390,6 +390,29 @@ func dohTestPacket(msgID uint16) []byte { return body } +func dohNXTestPacket(msgID uint16) []byte { + msg := dns.Msg{} + qName := make([]byte, 16) + charset := "abcdefghijklmnopqrstuvwxyz" + for i := range qName { + qName[i] = charset[rand.Intn(len(charset))] + } + msg.SetQuestion(string(qName)+".dnscrypt.test.", dns.TypeNS) + msg.Id = msgID + msg.MsgHdr.RecursionDesired = true + msg.SetEdns0(uint16(MaxDNSPacketSize), false) + ext := new(dns.EDNS0_PADDING) + ext.Padding = make([]byte, 16) + crypto_rand.Read(ext.Padding) + edns0 := msg.IsEdns0() + edns0.Option = append(edns0.Option, ext) + body, err := msg.Pack() + if err != nil { + dlog.Fatal(err) + } + return body +} + func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isNew bool) (ServerInfo, error) { // If an IP has been provided, use it forever. // Or else, if the fallback server and the DoH server are operated @@ -424,6 +447,7 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN } dlog.Debugf("Server [%s] doesn't appear to support POST; falling back to GET requests", name) } + body = dohNXTestPacket(0xcafe) serverResponse, tls, rtt, err := proxy.xTransport.DoHQuery(useGet, url, body, proxy.timeout) if err != nil { return ServerInfo{}, err @@ -431,6 +455,13 @@ func fetchDoHServerInfo(proxy *Proxy, name string, stamp stamps.ServerStamp, isN if tls == nil || !tls.HandshakeComplete { return ServerInfo{}, errors.New("TLS handshake failed") } + msg := dns.Msg{} + if err := msg.Unpack(serverResponse); err != nil { + return ServerInfo{}, err + } + if msg.Rcode != dns.RcodeNameError { + dlog.Criticalf("[%s] may be a lying resolver", name) + } protocol := tls.NegotiatedProtocol if len(protocol) == 0 { protocol = "h1"