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>
This commit is contained in:
parent
df86576989
commit
6f2bae019f
|
@ -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}},
|
||||||
|
@ -88,26 +88,28 @@ func TestNameserver(t *testing.T) {
|
||||||
wantResp: &dns.Msg{
|
wantResp: &dns.Msg{
|
||||||
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…
Reference in New Issue