From 1fd10061fdfba0062a1754470151524e2b9c3a45 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Wed, 23 Sep 2020 12:05:51 -0700 Subject: [PATCH] wgengine/tsdns: delegate bonjour service rdns requests While we're here, parseQuery into a plain function. This is helpful for fuzzing. (Which I did a bit of. Didn't find anything.) And clean up a few minor things. Signed-off-by: Josh Bleecher Snyder --- wgengine/tsdns/tsdns.go | 41 ++++++++++++++++++++++++++++++++---- wgengine/tsdns/tsdns_test.go | 23 ++++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/wgengine/tsdns/tsdns.go b/wgengine/tsdns/tsdns.go index d2021bf1e..d260c8041 100644 --- a/wgengine/tsdns/tsdns.go +++ b/wgengine/tsdns/tsdns.go @@ -299,7 +299,7 @@ type response struct { } // parseQuery parses the query in given packet into a response struct. -func (r *Resolver) parseQuery(query []byte, resp *response) error { +func parseQuery(query []byte, resp *response) error { var parser dns.Parser var err error @@ -423,6 +423,35 @@ const ( rdnsv6Suffix = ".ip6.arpa." ) +// hasRDNSBonjourPrefix reports whether name has a Bonjour Service Prefix.. +// +// https://tools.ietf.org/html/rfc6763 lists +// "five special RR names" for Bonjour service discovery: +// +// b._dns-sd._udp.. +// db._dns-sd._udp.. +// r._dns-sd._udp.. +// dr._dns-sd._udp.. +// lb._dns-sd._udp.. +func hasRDNSBonjourPrefix(s string) bool { + // Even the shortest name containing a Bonjour prefix is long, + // so check length (cheap) and bail early if possible. + if len(s) < len("*._dns-sd._udp.0.0.0.0.in-addr.arpa.") { + return false + } + dot := strings.IndexByte(s, '.') + if dot == -1 { + return false // shouldn't happen + } + switch s[:dot] { + case "b", "db", "r", "dr", "lb": + default: + return false + } + + return strings.HasPrefix(s[dot:], "._dns-sd._udp.") +} + // rawNameToLower converts a raw DNS name to a string, lowercasing it. func rawNameToLower(name []byte) string { var sb strings.Builder @@ -502,9 +531,12 @@ func rdnsNameToIPv6(name string) (ip netaddr.IP, ok bool) { // respondReverse returns a DNS response to a PTR query. // It is assumed that resp.Question is populated by respond before this is called. func (r *Resolver) respondReverse(query []byte, name string, resp *response) ([]byte, error) { + if hasRDNSBonjourPrefix(name) { + return nil, errNotOurName + } + var ip netaddr.IP var ok bool - var err error switch { case strings.HasSuffix(name, rdnsv4Suffix): ip, ok = rdnsNameToIPv4(name) @@ -521,6 +553,7 @@ func (r *Resolver) respondReverse(query []byte, name string, resp *response) ([] return nil, errNotOurName } + var err error resp.Name, resp.Header.RCode, err = r.ResolveReverse(ip) if err != nil { r.logf("resolving rdns: %v", ip, err) @@ -540,7 +573,7 @@ func (r *Resolver) respond(query []byte) ([]byte, error) { // ParseQuery is sufficiently fast to run on every DNS packet. // This is considerably simpler than extracting the name by hand // to shave off microseconds in case of delegation. - err := r.parseQuery(query, resp) + err := parseQuery(query, resp) // We will not return this error: it is the sender's fault. if err != nil { r.logf("parsing query: %v", err) @@ -551,7 +584,7 @@ func (r *Resolver) respond(query []byte) ([]byte, error) { name := rawNameToLower(rawName) // Always try to handle reverse lookups; delegate inside when not found. - // This way, queries for exitent nodes do not leak, + // This way, queries for existent nodes do not leak, // but we behave gracefully if non-Tailscale nodes exist in CGNATRange. if resp.Question.Type == dns.TypePTR { return r.respondReverse(query, name, resp) diff --git a/wgengine/tsdns/tsdns_test.go b/wgengine/tsdns/tsdns_test.go index 944d44dd2..b072fc710 100644 --- a/wgengine/tsdns/tsdns_test.go +++ b/wgengine/tsdns/tsdns_test.go @@ -684,6 +684,29 @@ func TestAllocs(t *testing.T) { } } +func TestTrimRDNSBonjourPrefix(t *testing.T) { + tests := []struct { + in string + want bool + }{ + {"b._dns-sd._udp.0.10.20.172.in-addr.arpa.", true}, + {"db._dns-sd._udp.0.10.20.172.in-addr.arpa.", true}, + {"r._dns-sd._udp.0.10.20.172.in-addr.arpa.", true}, + {"dr._dns-sd._udp.0.10.20.172.in-addr.arpa.", true}, + {"lb._dns-sd._udp.0.10.20.172.in-addr.arpa.", true}, + {"qq._dns-sd._udp.0.10.20.172.in-addr.arpa.", false}, + {"0.10.20.172.in-addr.arpa.", false}, + {"i-have-no-dot", false}, + } + + for _, test := range tests { + got := hasRDNSBonjourPrefix(test.in) + if got != test.want { + t.Errorf("trimRDNSBonjourPrefix(%q) = %v, want %v", test.in, got, test.want) + } + } +} + func BenchmarkFull(b *testing.B) { dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))