Subject: Re: [Doh] Clarification for a newbie DoH implementor

From: "Mark Delany" <[email-masked]>
Date: 9 Jun 2019 08:37:24 +0000

On 19May19, Ben Schwartz allegedly wrote:

> In practice, none of this seems to be a problem.  DoH servers simply do
> their best to fully answer the query

I know this is getting close to flogging a dead horse, but I'm still troubled by
the silent truncation implied by a DoH Server processing TC=1.

Notwithstanding clients which directly connect to DoH servers (such as some
browsers) the typical scenario is likely to be existing stub clients using UDP
to talk to a DoH proxy/client which in turn talks to a DoH server, i.e.:

 stub (UDP) ->        DoH proxy (HTTPS) -> DoH server (UDP) -> resolver
 stub (UDP) <-        DoH proxy (HTTPS) <- DoH server (UDP) <- resolver

(Sorry, you'll need to render this in a fixed-width font).

As I understand Ben's response, if the resolver returns TC=1 then a typical DoH
server (or its resolver library) will retry with TCP which makes the flow for a
large response actually look like:

 stub (UDP) ->        DoH proxy (HTTPS) -> DoH server (UDP) -> resolver
                                           DoH server (UDP) <- (TC=1) resolver
                                           DoH server (TCP) -> resolver
 stub (UDP) <- (TC=0) DoH proxy (HTTPS) <- DoH server (TCP) <- (TC=0) resolver

Which certainly meets the "do their best to fully answer the query" as the stub
never has to worry about TC=1 and simply gets the "full result".

However... It makes me wonder how a large TCP response the DoH Server sends back
via HTTPS can possibly fit into the response the DoH proxy sends back via UDP to
the stub.

Let's say the DoH proxy receives a 5K response over HTTPS, does it blindly try
and transmit this 5K response over UDP back to the stub and just hope for the
best? Or should it know this is likely to fail and act accordingly? If so, how
should it act?

I can't think of what a DoH proxy can sensibly do in such circumstances apart
from arbitrarily truncate the response down to the UDP size indicated by the
stub and *not* mark the response with TC=1. Thus the stub loses some of the
answer and more importantly loses knowledge of the truncation. That seems bad to
me.

As we've discussed previously, it's pointless returning TC=1 to the stub as it
will simply re-issue an identical query as far as the DoH server is concerned.

Note that I said "identical query as far as the DoH server is concerned". The
same is not the case for the DoH proxy as the query re-issued from the stub
*can* be disambiguated since the stub connects to the proxy via TCP.

Which offers a possible solution.

Since a proxy knows whether the inbound query has come via UDP or TCP it can
annotate the HTTPS request accordingly (let's invent a "Use-TCP"
header). The DoH server acts on this header thus the flow becomes:

 stub (UDP) ->        DoH proxy (HTTPS)           -> DoH server (UDP) -> resolver
 stub (UDP) <- (TC=1) DoH proxy (HTTPS)           <- DoH server (UDP) <- (TC=1) resolver
 stub (TCP) ->        DoH proxy (HTTPS - Use-TCP) -> DoH server (TCP) -> resolver
 stub (TCP) <- (TC=0) DoH proxy (HTTPS)           <- DoH server (TCP) <- (TC=0) resolver

IOWs TC=1 is sent all the way back to the stub for it to deal with. If the stub
re-queries with TCP, the proxy forwards the query with the "Use-TCP" annotation
to the DoH server which in turn instructs its resolver library to use TCP.

Not only does this alleviate a DoH server from trying their best to "fully
answer the query" in the blind, it also avoids the impossibility of transmitting
a large response back to the stub over UDP. Most importantly truncation is no
longer silently performed - rather it is communicated back to the stub which
lets it decide what to do as has traditionally been the case.

I think all stubs should work in this "Use-TCP" scenario whereas clearly they
cannot in the "DoH server transparently handles TC=1" scenario.

The one down-side is that a TC=1 response incurs higher latency due to the flow
going all the way back to the stub for a retry, however given these TC=1
responses are uncommon, that seems like a minor price to pay for a consistent
outcome.

Mark.

---

From: Ben Schwartz <[email-masked]>
Date: Sun, 9 Jun 2019 13:04:22 -0400

On Sun, Jun 9, 2019, 4:37 AM Mark Delany <[email-masked]> wrote:

> stub and *not* mark the response with TC=1.

I don't understand your logic.  From my perspective, the obvious thing for
this proxy to do is to truncate the response, and set TC=1 if truncation
occurred.  Truncating without setting TC=1 seems like a violation of the
DNS protocol.

Thus the stub loses some of the
> *can* be disambiguated since the stub connects to the proxy via TCP.

Indeed.  It seems obvious to me that the proxy will not truncate the
response to a query that arrived over TCP, since truncation is not required
for DNS-over-TCP, and therefore the full query will succeed from the stub's
perspective.  To summarize, a proxy that does the simplest thing to comply
with DNS and DoH RFCs will correctly deliver full responses to the stub
without requiring any changes to either protocol.

> _______________________________________________
> Doh mailing list
> [email-masked]
> https://www.ietf.org/mailman/listinfo/doh
>

---

From: "Mark Delany" <[email-masked]>
Date: 9 Jun 2019 20:08:20 +0000

> I don't understand your logic.

Bah humbug. Nor do I now that I have a bit more of a think about
it. Just shows that coming back fresh isn't all it's cracked up to
be...

For some reason I was thinking the stub would retry with UDP which of
course it won't.

It's just an observation but if a DoH server "fully answers" it means
that when a proxy does have to locally truncate a response to fit into
UDP it means that the DoH server will ultimately issue the query four
times.

Stub UDP      -> Proxy HTTPS -> DoH Server ->      UDP resolver Query 1
                                           <- TC=1 UDP
                                           ->      TCP          Query 2
     UDP TC=1 <- (proxy truncates          <-      TCP
                  to fit into UDP)

(Stub retries with TCP due to TC=1 from proxy)

Stub TCP      -> Proxy HTTPS -> DoH Server ->      UDP resolver Query 3
                                           <- TC=1 UDP
                                           ->      TCP          Query 4
     UDP TC=0 <- (proxy doesn't need to    <-      TCP
                  truncate for TCP)

No big deal I suppose since over-sized responses are meant to be a
uncommon and of course the proxy could cache locally truncated
responses for a couple of seconds if it wanted to bother.

Mark.

---

From: Tony Finch <[email-masked]>
Date: Wed, 19 Jun 2019 17:05:01 +0100

> will simply re-issue an identical query as far as the DoH server is concerned.

TC=1 implies that the client should retry using a different transport
that can handle larger messages. If your transport is not UDP, there
aren't any alternatives that can handle larger messages :-)

You *can* get TC=1 on TCP etc, because it is possible to create an RRset
that is larger than 65536 octets.

Tony.
-- 
f.anthony.n.finch  <[email-masked]>  http://dotat.at/
the widest possible distribution of wealth