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 <irbe@tailscale.com>
pull/12418/head
Irbe Krumina 5 months ago committed by GitHub
parent df86576989
commit 6f2bae019f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -153,11 +153,36 @@ func (n *nameserver) handleFunc() func(w dns.ResponseWriter, r *dns.Msg) {
m.Answer = append(m.Answer, rr) m.Answer = append(m.Answer, rr)
} }
case dns.TypeAAAA: case dns.TypeAAAA:
// TODO (irbekrm): implement IPv6 support. // TODO (irbekrm): add IPv6 support.
// Kubernetes distributions that I am most familiar with // The nameserver currently does not support IPv6
// default to IPv4 for Pod CIDR ranges and often many cases don't // (records are not being created for IPv6 Pod addresses).
// support IPv6 at all, so this should not be crucial for now. // However, we can expect that some callers will
fallthrough // 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: default:
log.Printf("[unexpected] nameserver received a query for an unsupported record type: %s", r.Question[0].String()) log.Printf("[unexpected] nameserver received a query for an unsupported record type: %s", r.Question[0].String())
m.SetRcode(r, dns.RcodeNotImplemented) m.SetRcode(r, dns.RcodeNotImplemented)

@ -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}}}, ip4: map[dnsname.FQDN][]net.IP{dnsname.FQDN("foo.bar.com."): {{1, 2, 3, 4}}},
query: &dns.Msg{ query: &dns.Msg{
Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}},
@ -89,25 +89,27 @@ func TestNameserver(t *testing.T) {
Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}}, Question: []dns.Question{{Name: "foo.bar.com", Qtype: dns.TypeAAAA}},
MsgHdr: dns.MsgHdr{ MsgHdr: dns.MsgHdr{
Id: 1, Id: 1,
Rcode: dns.RcodeNotImplemented, Rcode: dns.RcodeSuccess,
Response: true, Response: true,
Opcode: dns.OpcodeQuery, 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}}}, ip4: map[dnsname.FQDN][]net.IP{dnsname.FQDN("foo.bar.com."): {{1, 2, 3, 4}}},
query: &dns.Msg{ 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}, MsgHdr: dns.MsgHdr{Id: 1},
}, },
wantResp: &dns.Msg{ 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{ MsgHdr: dns.MsgHdr{
Id: 1, Id: 1,
Rcode: dns.RcodeNotImplemented, Rcode: dns.RcodeNameError,
Response: true, Response: true,
Opcode: dns.OpcodeQuery, Opcode: dns.OpcodeQuery,
Authoritative: true,
}}, }},
}, },
{ {

Loading…
Cancel
Save