author | Alberto Bertogli
<albertito@blitiri.com.ar> 2021-06-12 10:57:26 UTC |
committer | Alberto Bertogli
<albertito@blitiri.com.ar> 2021-06-12 14:21:40 UTC |
parent | b5631eac24e6cbfddd36fdbfe01ad085d50a7afe |
internal/dnsserver/resolver.go | +2 | -0 |
internal/dnsserver/server.go | +19 | -2 |
tests/external.sh | +28 | -1 |
diff --git a/internal/dnsserver/resolver.go b/internal/dnsserver/resolver.go index 73666cb..86db83d 100644 --- a/internal/dnsserver/resolver.go +++ b/internal/dnsserver/resolver.go @@ -194,6 +194,8 @@ func wantToCache(question dns.Question, reply *dns.Msg) error { return fmt.Errorf("answer is empty") } else if len(reply.Question) != 1 { return fmt.Errorf("too many/few questions (%d)", len(reply.Question)) + } else if reply.Truncated { + return fmt.Errorf("truncated reply") } else if reply.Question[0] != question { return fmt.Errorf( "reply question does not match: asked %v, got %v", diff --git a/internal/dnsserver/server.go b/internal/dnsserver/server.go index 71d61f6..e26c6dd 100644 --- a/internal/dnsserver/server.go +++ b/internal/dnsserver/server.go @@ -86,7 +86,7 @@ func (s *Server) Handler(w dns.ResponseWriter, r *dns.Msg) { if err == nil { tr.Printf("used unqualified upstream") tr.Answer(u) - w.WriteMsg(u) + s.writeReply(tr, w, r, u) } else { tr.Printf("unqualified upstream error: %v", err) dns.HandleFailed(w, r) @@ -113,7 +113,24 @@ func (s *Server) Handler(w dns.ResponseWriter, r *dns.Msg) { tr.Answer(fromUp) fromUp.Id = oldid - w.WriteMsg(fromUp) + s.writeReply(tr, w, r, fromUp) +} + +func (s *Server) writeReply(tr *trace.Trace, w dns.ResponseWriter, r, reply *dns.Msg) { + if w.RemoteAddr().Network() == "udp" { + // We need to check if the response fits. + // UDP by default has a maximum of 512 bytes. This can be extended via + // the client in the EDNS0 record. + max := 512 + ednsOPT := r.IsEdns0() + if ednsOPT != nil { + max = int(ednsOPT.UDPSize()) + } + reply.Truncate(max) + tr.Printf("UDP max:%d truncated:%v", max, reply.Truncated) + } + + w.WriteMsg(reply) } // ListenAndServe launches the DNS proxy. diff --git a/tests/external.sh b/tests/external.sh index 2d21def..b7dab5b 100755 --- a/tests/external.sh +++ b/tests/external.sh @@ -87,6 +87,34 @@ function resolve() { cat .dig.log false fi + + # The response exceeds the default UDP size (512b), so it should fall back + # to TCP. This exercises the truncating logic. + kdig @127.0.0.1:1053 google.com TXT > .dig.log 2>&1 + if ! grep -E -i -q '^google.com.*TXT' .dig.log; then + echo "----- FAILED (missing response)" + cat .dig.log + false + fi + if ! grep -E -i -q 'retrying over TCP' .dig.log; then + echo "----- FAILED (did not use TCP)" + cat .dig.log + false + fi + + # Same as above, but we explicitly are ok with a 2k response, which + # fits the reply. We check that we do NOT fall back to TCP. + kdig @127.0.0.1:1053 +bufsize=2048 google.com TXT > .dig.log 2>&1 + if ! grep -E -i -q '^google.com.*TXT' .dig.log; then + echo "----- FAILED (missing response)" + cat .dig.log + false + fi + if grep -E -i -q 'retrying over TCP' .dig.log; then + echo "----- FAILED (used TCP)" + cat .dig.log + false + fi } # HTTP GET, using wget. @@ -190,7 +218,6 @@ for server in \ "https://cloudflare-dns.com/dns-query" \ "https://dns.google/dns-query" \ "https://dns.quad9.net/dns-query" \ - "https://doh.cleanbrowsing.org/doh/family-filter/" \ "https://doh.powerdns.org" \ ; do