From 6f2bae019f0fb30cbd2a4193c7c917a0c486eeed Mon Sep 17 00:00:00 2001 From: Irbe Krumina Date: Mon, 10 Jun 2024 17:57:22 +0100 Subject: [PATCH] cmd/k8s-nameserver: fix AAAA record query response (#12412) Return empty response and NOERROR for AAAA record queries for DNS names for which we have an A record. This is to allow for callers that might be first sending an AAAA query and then, if that does not return a response, follow with an A record query. Previously we were returning NOTIMPL that caused some callers to potentially not follow with an A record query or misbehave in different ways. Also return NXDOMAIN for AAAA record queries for names that we DO NOT have an A record for to ensure that the callers do not follow up with an A record query. Returning an empty response and NOERROR is the behaviour that RFC 4074 recommends: https://datatracker.ietf.org/doc/html/rfc4074 Updates tailscale/tailscale#12321 Signed-off-by: Irbe Krumina --- cmd/k8s-nameserver/main.go | 35 ++++++++++++++++++++++++++++----- cmd/k8s-nameserver/main_test.go | 26 +++++++++++++----------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/cmd/k8s-nameserver/main.go b/cmd/k8s-nameserver/main.go index 53c0fee39..ca4b44935 100644 --- a/cmd/k8s-nameserver/main.go +++ b/cmd/k8s-nameserver/main.go @@ -153,11 +153,36 @@ func (n *nameserver) handleFunc() func(w dns.ResponseWriter, r *dns.Msg) { m.Answer = append(m.Answer, rr) } case dns.TypeAAAA: - // TODO (irbekrm): implement IPv6 support. - // Kubernetes distributions that I am most familiar with - // default to IPv4 for Pod CIDR ranges and often many cases don't - // support IPv6 at all, so this should not be crucial for now. - fallthrough + // TODO (irbekrm): add IPv6 support. + // The nameserver currently does not support IPv6 + // (records are not being created for IPv6 Pod addresses). + // However, we can expect that some callers will + // nevertheless send AAAA queries. + // We have to return NOERROR if a query is received for + // an AAAA record for a DNS name that we have an A + // record for- else the caller might not follow with an + // A record query. + // https://github.com/tailscale/tailscale/issues/12321 + // https://datatracker.ietf.org/doc/html/rfc4074 + q := r.Question[0].Name + fqdn, err := dnsname.ToFQDN(q) + if err != nil { + m = r.SetRcodeFormatError(r) + return + } + // The only supported use of this nameserver is as a + // single source of truth for MagicDNS names by + // non-tailnet Kubernetes workloads. + m.Authoritative = true + ips := n.lookupIP4(fqdn) + if len(ips) == 0 { + // As we are the authoritative nameserver for MagicDNS + // names, if we do not have a record for this MagicDNS + // name, it does not exist. + m = m.SetRcode(r, dns.RcodeNameError) + return + } + m.SetRcode(r, dns.RcodeSuccess) default: log.Printf("[unexpected] nameserver received a query for an unsupported record type: %s", r.Question[0].String()) m.SetRcode(r, dns.RcodeNotImplemented) diff --git a/cmd/k8s-nameserver/main_test.go b/cmd/k8s-nameserver/main_test.go index 2c0367e6e..d9a33c4fa 100644 --- a/cmd/k8s-nameserver/main_test.go +++ b/cmd/k8s-nameserver/main_test.go @@ -79,7 +79,7 @@ func TestNameserver(t *testing.T) { }}, }, { - name: "AAAA record query", + name: "AAAA record query, A record exists", ip4: map[dnsname.FQDN][]net.IP{dnsname.FQDN("foo.bar.com."): {{1, 2, 3, 4}}}, query: &dns.Msg{ Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, @@ -88,26 +88,28 @@ func TestNameserver(t *testing.T) { wantResp: &dns.Msg{ Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, MsgHdr: dns.MsgHdr{ - Id: 1, - Rcode: dns.RcodeNotImplemented, - Response: true, - Opcode: dns.OpcodeQuery, + Id: 1, + Rcode: dns.RcodeSuccess, + Response: true, + Opcode: dns.OpcodeQuery, + Authoritative: true, }}, }, { - name: "AAAA record query", + name: "AAAA record query, A record does not exist", ip4: map[dnsname.FQDN][]net.IP{dnsname.FQDN("foo.bar.com."): {{1, 2, 3, 4}}}, query: &dns.Msg{ - Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, + Question: []dns.Question{{Name: "baz.bar.com", Qtype: dns.TypeAAAA}}, MsgHdr: dns.MsgHdr{Id: 1}, }, wantResp: &dns.Msg{ - Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, + Question: []dns.Question{{Name: "baz.bar.com", Qtype: dns.TypeAAAA}}, MsgHdr: dns.MsgHdr{ - Id: 1, - Rcode: dns.RcodeNotImplemented, - Response: true, - Opcode: dns.OpcodeQuery, + Id: 1, + Rcode: dns.RcodeNameError, + Response: true, + Opcode: dns.OpcodeQuery, + Authoritative: true, }}, }, {