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 <josh@tailscale.com>reviewable/pr788/r2
parent
2d0ed99672
commit
1fd10061fd
|
@ -299,7 +299,7 @@ type response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseQuery parses the query in given packet into a 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 parser dns.Parser
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
@ -423,6 +423,35 @@ const (
|
||||||
rdnsv6Suffix = ".ip6.arpa."
|
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.<domain>.
|
||||||
|
// db._dns-sd._udp.<domain>.
|
||||||
|
// r._dns-sd._udp.<domain>.
|
||||||
|
// dr._dns-sd._udp.<domain>.
|
||||||
|
// lb._dns-sd._udp.<domain>.
|
||||||
|
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.
|
// rawNameToLower converts a raw DNS name to a string, lowercasing it.
|
||||||
func rawNameToLower(name []byte) string {
|
func rawNameToLower(name []byte) string {
|
||||||
var sb strings.Builder
|
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.
|
// respondReverse returns a DNS response to a PTR query.
|
||||||
// It is assumed that resp.Question is populated by respond before this is called.
|
// 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) {
|
func (r *Resolver) respondReverse(query []byte, name string, resp *response) ([]byte, error) {
|
||||||
|
if hasRDNSBonjourPrefix(name) {
|
||||||
|
return nil, errNotOurName
|
||||||
|
}
|
||||||
|
|
||||||
var ip netaddr.IP
|
var ip netaddr.IP
|
||||||
var ok bool
|
var ok bool
|
||||||
var err error
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasSuffix(name, rdnsv4Suffix):
|
case strings.HasSuffix(name, rdnsv4Suffix):
|
||||||
ip, ok = rdnsNameToIPv4(name)
|
ip, ok = rdnsNameToIPv4(name)
|
||||||
|
@ -521,6 +553,7 @@ func (r *Resolver) respondReverse(query []byte, name string, resp *response) ([]
|
||||||
return nil, errNotOurName
|
return nil, errNotOurName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
resp.Name, resp.Header.RCode, err = r.ResolveReverse(ip)
|
resp.Name, resp.Header.RCode, err = r.ResolveReverse(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("resolving rdns: %v", ip, err)
|
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.
|
// ParseQuery is sufficiently fast to run on every DNS packet.
|
||||||
// This is considerably simpler than extracting the name by hand
|
// This is considerably simpler than extracting the name by hand
|
||||||
// to shave off microseconds in case of delegation.
|
// 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.
|
// We will not return this error: it is the sender's fault.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logf("parsing query: %v", err)
|
r.logf("parsing query: %v", err)
|
||||||
|
@ -551,7 +584,7 @@ func (r *Resolver) respond(query []byte) ([]byte, error) {
|
||||||
name := rawNameToLower(rawName)
|
name := rawNameToLower(rawName)
|
||||||
|
|
||||||
// Always try to handle reverse lookups; delegate inside when not found.
|
// 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.
|
// but we behave gracefully if non-Tailscale nodes exist in CGNATRange.
|
||||||
if resp.Question.Type == dns.TypePTR {
|
if resp.Question.Type == dns.TypePTR {
|
||||||
return r.respondReverse(query, name, resp)
|
return r.respondReverse(query, name, resp)
|
||||||
|
|
|
@ -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) {
|
func BenchmarkFull(b *testing.B) {
|
||||||
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
dnsHandleFunc("test.site.", resolveToIP(testipv4, testipv6, "dns.test.site."))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue