From 4cdc4ed7db6bab75b62d065e915b4fc2a5ce6aa5 Mon Sep 17 00:00:00 2001 From: Nick Khyl Date: Mon, 10 Jun 2024 18:34:20 -0500 Subject: [PATCH] net/dns/resolver: return an empty successful response instead of NXDomain when resolving A records for 4via6 domains As quad-100 is an authoritative server for 4via6 domains, it should always return responses with a response code of 0 (indicating no error) when resolving records for these domains. If there's no resource record of the specified type (e.g. A), it should return a response with an empty answer section rather than NXDomain. Such a response indicates that there is at least one RR of a different type (e.g., AAAA), suggesting the Windows stub resolver to look for it. Fixes tailscale/corp#20767 Signed-off-by: Nick Khyl --- net/dns/resolver/tsdns.go | 26 ++++++++++++++++++++++---- net/dns/resolver/tsdns_test.go | 6 ++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/net/dns/resolver/tsdns.go b/net/dns/resolver/tsdns.go index dd0504baf..a2837e0ec 100644 --- a/net/dns/resolver/tsdns.go +++ b/net/dns/resolver/tsdns.go @@ -610,7 +610,7 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, } } // Special-case: 4via6 DNS names. - if ip, ok := r.parseViaDomain(domain, typ); ok { + if ip, ok := r.resolveViaDomain(domain, typ); ok { return ip, dns.RCodeSuccess } @@ -689,7 +689,7 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, } } -// parseViaDomain synthesizes an IP address for quad-A DNS requests of the form +// resolveViaDomain synthesizes an IP address for quad-A DNS requests of the form // `-via-[.*]`. Two prior formats that // didn't pan out (due to a Chrome issue and DNS search ndots issues) were // `.via-` and the older `via-.`, @@ -697,13 +697,27 @@ func (r *Resolver) resolveLocal(domain dnsname.FQDN, typ dns.Type) (netip.Addr, // // This exists as a convenient mapping into Tailscales 'Via Range'. // +// It returns a zero netip.Addr and true to indicate a successful response with +// an empty answers section if the specified domain is a valid Tailscale 4via6 +// domain, but the request type is neither quad-A nor ALL. +// // TODO(maisem/bradfitz/tom): `.via-` was introduced // (2022-06-02) to work around an issue in Chrome where it would treat // "http://via-1.1.2.3.4" as a search string instead of a URL. We should rip out // the old format in early 2023. -func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr, bool) { +func (r *Resolver) resolveViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr, bool) { fqdn := string(domain.WithoutTrailingDot()) - if typ != dns.TypeAAAA { + switch typ { + case dns.TypeA, dns.TypeAAAA, dns.TypeALL: + // For Type A requests, we should return a successful response + // with an empty answer section rather than an NXDomain + // if the specified domain is a valid Tailscale 4via6 domain. + // + // Therefore, we should continue and parse the domain name first + // before deciding whether to return an IPv6 address, + // a zero (invalid) netip.Addr and true to indicate a successful empty response, + // or a zero netip.Addr and false to indicate that it is not a Tailscale 4via6 domain. + default: return netip.Addr{}, false } if len(fqdn) < len("via-X.0.0.0.0") { @@ -756,6 +770,10 @@ func (r *Resolver) parseViaDomain(domain dnsname.FQDN, typ dns.Type) (netip.Addr return netip.Addr{}, false // badly formed, don't respond } + if typ == dns.TypeA { + return netip.Addr{}, true // the name exists, but cannot be resolved to an IPv4 address + } + // MapVia will never error when given an IPv4 netip.Prefix. out, _ := tsaddr.MapVia(uint32(prefix), netip.PrefixFrom(ip4, ip4.BitLen())) return out.Addr(), true diff --git a/net/dns/resolver/tsdns_test.go b/net/dns/resolver/tsdns_test.go index fb33b49a8..e1477e342 100644 --- a/net/dns/resolver/tsdns_test.go +++ b/net/dns/resolver/tsdns_test.go @@ -401,6 +401,12 @@ func TestResolveLocal(t *testing.T) { // suffixes are currently hard-coded and not plumbed via the netmap) {"via_form3_dec_example.com", dnsname.FQDN("1-2-3-4-via-1.example.com."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused}, {"via_form3_dec_examplets.net", dnsname.FQDN("1-2-3-4-via-1.examplets.net."), dns.TypeAAAA, netip.Addr{}, dns.RCodeRefused}, + + // Resolve A and ALL types of resource records. + {"via_type_a", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeA, netip.Addr{}, dns.RCodeSuccess}, + {"via_invalid_type_a", dnsname.FQDN("1-2-3-4-via-."), dns.TypeA, netip.Addr{}, dns.RCodeRefused}, + {"via_type_all", dnsname.FQDN("1-2-3-4-via-1."), dns.TypeALL, netip.MustParseAddr("fd7a:115c:a1e0:b1a:0:1:1.2.3.4"), dns.RCodeSuccess}, + {"via_invalid_type_all", dnsname.FQDN("1-2-3-4-via-."), dns.TypeALL, netip.Addr{}, dns.RCodeRefused}, } for _, tt := range tests {