dns: provide correct SOA and NS responses
This patch changes the behavior of the DNS server as follows: * The SOA response contains the SOA record in the Answer section instead of the Authority section. It also contains NS records in the Authority and the corresponding A glue records in the Extra section. In addition, CNAMEs are added to the Extra section to make the MNAME of the SOA record resolvable. AAAA glue records are not yet supported. * The NS response returns up to three random servers from the consul cluster in the Answer section and the glue A records in the Extra section. AAAA glue records are not yet supported.
This commit is contained in:
parent
bff45ee1da
commit
450d8a69b5
183
agent/dns.go
183
agent/dns.go
|
@ -137,7 +137,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
|
|
||||||
// Only add the SOA if requested
|
// Only add the SOA if requested
|
||||||
if req.Question[0].Qtype == dns.TypeSOA {
|
if req.Question[0].Qtype == dns.TypeSOA {
|
||||||
d.addSOA(d.domain, m)
|
d.addSOA(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
datacenter := d.agent.config.Datacenter
|
datacenter := d.agent.config.Datacenter
|
||||||
|
@ -210,13 +210,40 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
m.Authoritative = true
|
m.Authoritative = true
|
||||||
m.RecursionAvailable = (len(d.recursors) > 0)
|
m.RecursionAvailable = (len(d.recursors) > 0)
|
||||||
|
|
||||||
// Only add the SOA if requested
|
switch req.Question[0].Qtype {
|
||||||
if req.Question[0].Qtype == dns.TypeSOA {
|
case dns.TypeSOA:
|
||||||
d.addSOA(d.domain, m)
|
ns, glue := d.nameservers()
|
||||||
}
|
m.Answer = append(m.Answer, d.soa())
|
||||||
|
m.Ns = append(m.Ns, ns...)
|
||||||
|
m.Extra = append(m.Extra, glue...)
|
||||||
|
|
||||||
// Dispatch the correct handler
|
// add CNAMEs for "ns.<domain>" to the Extra
|
||||||
d.dispatch(network, req, m)
|
// section to make the MNAME entry in the SOA
|
||||||
|
// record resolvable
|
||||||
|
for _, rr := range ns {
|
||||||
|
cname := &dns.CNAME{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: "ns." + d.domain,
|
||||||
|
Rrtype: dns.TypeCNAME,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: uint32(d.config.NodeTTL / time.Second),
|
||||||
|
},
|
||||||
|
Target: rr.(*dns.NS).Ns,
|
||||||
|
}
|
||||||
|
m.Extra = append(m.Extra, cname)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.SetRcode(req, dns.RcodeSuccess)
|
||||||
|
|
||||||
|
case dns.TypeNS:
|
||||||
|
ns, glue := d.nameservers()
|
||||||
|
m.Answer = ns
|
||||||
|
m.Extra = glue
|
||||||
|
m.SetRcode(req, dns.RcodeSuccess)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.dispatch(network, req, m)
|
||||||
|
}
|
||||||
|
|
||||||
// Handle EDNS
|
// Handle EDNS
|
||||||
if edns := req.IsEdns0(); edns != nil {
|
if edns := req.IsEdns0(); edns != nil {
|
||||||
|
@ -229,24 +256,89 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addSOA is used to add an SOA record to a message for the given domain
|
func (d *DNSServer) soa() *dns.SOA {
|
||||||
func (d *DNSServer) addSOA(domain string, msg *dns.Msg) {
|
return &dns.SOA{
|
||||||
soa := &dns.SOA{
|
|
||||||
Hdr: dns.RR_Header{
|
Hdr: dns.RR_Header{
|
||||||
Name: domain,
|
Name: d.domain,
|
||||||
Rrtype: dns.TypeSOA,
|
Rrtype: dns.TypeSOA,
|
||||||
Class: dns.ClassINET,
|
Class: dns.ClassINET,
|
||||||
Ttl: 0,
|
Ttl: 0,
|
||||||
},
|
},
|
||||||
Ns: "ns." + domain,
|
Ns: "ns." + d.domain,
|
||||||
Mbox: "postmaster." + domain,
|
Serial: uint32(time.Now().Unix()),
|
||||||
Serial: uint32(time.Now().Unix()),
|
|
||||||
|
// todo(fs): make these configurable
|
||||||
|
Mbox: "postmaster." + d.domain,
|
||||||
Refresh: 3600,
|
Refresh: 3600,
|
||||||
Retry: 600,
|
Retry: 600,
|
||||||
Expire: 86400,
|
Expire: 86400,
|
||||||
Minttl: 0,
|
Minttl: 0,
|
||||||
}
|
}
|
||||||
msg.Ns = append(msg.Ns, soa)
|
}
|
||||||
|
|
||||||
|
// addSOA is used to add an SOA record to a message for the given domain
|
||||||
|
func (d *DNSServer) addSOA(msg *dns.Msg) {
|
||||||
|
msg.Ns = append(msg.Ns, d.soa())
|
||||||
|
}
|
||||||
|
|
||||||
|
// nameservers returns the names and ip addresses of up to three random servers
|
||||||
|
// in the current cluster which serve as authoritative name servers for zone.
|
||||||
|
func (d *DNSServer) nameservers() (ns []dns.RR, extra []dns.RR) {
|
||||||
|
// get server names and store them in a map to randomize the output
|
||||||
|
servers := map[string]net.IP{}
|
||||||
|
for name, addr := range d.agent.delegate.ServerAddrs() {
|
||||||
|
ip := net.ParseIP(strings.Split(addr, ":")[0])
|
||||||
|
if ip == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// name is "name.dc" and domain is "consul."
|
||||||
|
// we want "name.node.dc.consul."
|
||||||
|
lastdot := strings.LastIndexByte(name, '.')
|
||||||
|
fqdn := name[:lastdot] + ".node" + name[lastdot:] + "." + d.domain
|
||||||
|
|
||||||
|
// create a consistent, unique and sanitized name for the server
|
||||||
|
fqdn = dns.Fqdn(strings.ToLower(fqdn))
|
||||||
|
|
||||||
|
servers[fqdn] = ip
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(servers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, ip := range servers {
|
||||||
|
// the name server record
|
||||||
|
nsrr := &dns.NS{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: d.domain,
|
||||||
|
Rrtype: dns.TypeNS,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: 0,
|
||||||
|
},
|
||||||
|
Ns: name,
|
||||||
|
}
|
||||||
|
ns = append(ns, nsrr)
|
||||||
|
|
||||||
|
// the glue record providing the ip address
|
||||||
|
a := &dns.A{
|
||||||
|
Hdr: dns.RR_Header{
|
||||||
|
Name: name,
|
||||||
|
Rrtype: dns.TypeA,
|
||||||
|
Class: dns.ClassINET,
|
||||||
|
Ttl: uint32(d.config.NodeTTL / time.Second),
|
||||||
|
},
|
||||||
|
A: ip,
|
||||||
|
}
|
||||||
|
extra = append(extra, a)
|
||||||
|
|
||||||
|
// don't provide more than 3 servers
|
||||||
|
if len(ns) >= 3 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispatch is used to parse a request and invoke the correct handler
|
// dispatch is used to parse a request and invoke the correct handler
|
||||||
|
@ -375,8 +467,7 @@ PARSE:
|
||||||
return
|
return
|
||||||
INVALID:
|
INVALID:
|
||||||
d.logger.Printf("[WARN] dns: QName invalid: %s", qName)
|
d.logger.Printf("[WARN] dns: QName invalid: %s", qName)
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
d.addAuthority(resp)
|
|
||||||
resp.SetRcode(req, dns.RcodeNameError)
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,8 +509,7 @@ RPC:
|
||||||
|
|
||||||
// If we have no address, return not found!
|
// If we have no address, return not found!
|
||||||
if out.NodeServices == nil {
|
if out.NodeServices == nil {
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
d.addAuthority(resp)
|
|
||||||
resp.SetRcode(req, dns.RcodeNameError)
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -433,9 +523,6 @@ RPC:
|
||||||
if records != nil {
|
if records != nil {
|
||||||
resp.Answer = append(resp.Answer, records...)
|
resp.Answer = append(resp.Answer, records...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add NS record and A record
|
|
||||||
d.addAuthority(resp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatNodeRecord takes a Node and returns an A, AAAA, or CNAME record
|
// formatNodeRecord takes a Node and returns an A, AAAA, or CNAME record
|
||||||
|
@ -649,8 +736,7 @@ RPC:
|
||||||
|
|
||||||
// If we have no nodes, return not found!
|
// If we have no nodes, return not found!
|
||||||
if len(out.Nodes) == 0 {
|
if len(out.Nodes) == 0 {
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
d.addAuthority(resp)
|
|
||||||
resp.SetRcode(req, dns.RcodeNameError)
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -666,9 +752,6 @@ RPC:
|
||||||
d.serviceNodeRecords(datacenter, out.Nodes, req, resp, ttl)
|
d.serviceNodeRecords(datacenter, out.Nodes, req, resp, ttl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add NS and A records
|
|
||||||
d.addAuthority(resp)
|
|
||||||
|
|
||||||
// If the network is not TCP, restrict the number of responses
|
// If the network is not TCP, restrict the number of responses
|
||||||
if network != "tcp" {
|
if network != "tcp" {
|
||||||
wasTrimmed := trimUDPResponse(d.config, req, resp)
|
wasTrimmed := trimUDPResponse(d.config, req, resp)
|
||||||
|
@ -681,46 +764,11 @@ RPC:
|
||||||
|
|
||||||
// If the answer is empty and the response isn't truncated, return not found
|
// If the answer is empty and the response isn't truncated, return not found
|
||||||
if len(resp.Answer) == 0 && !resp.Truncated {
|
if len(resp.Answer) == 0 && !resp.Truncated {
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addAuthority adds NS records and corresponding A records with the IP addresses of servers
|
|
||||||
func (d *DNSServer) addAuthority(msg *dns.Msg) {
|
|
||||||
serverAddrs := d.agent.delegate.ServerAddrs()
|
|
||||||
for name, addr := range serverAddrs {
|
|
||||||
ipAddrStr := strings.Split(addr, ":")[0]
|
|
||||||
sanitizedName := InvalidDnsRe.ReplaceAllString(name, "-") // does some basic sanitization of the name
|
|
||||||
nsName := "server-" + sanitizedName + "." + d.domain
|
|
||||||
ip := net.ParseIP(ipAddrStr)
|
|
||||||
if ip != nil {
|
|
||||||
ns := &dns.NS{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: d.domain,
|
|
||||||
Rrtype: dns.TypeNS,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: 0,
|
|
||||||
},
|
|
||||||
Ns: nsName,
|
|
||||||
}
|
|
||||||
msg.Ns = append(msg.Ns, ns)
|
|
||||||
|
|
||||||
// add an A record for the NS record
|
|
||||||
a := &dns.A{
|
|
||||||
Hdr: dns.RR_Header{
|
|
||||||
Name: nsName,
|
|
||||||
Rrtype: dns.TypeA,
|
|
||||||
Class: dns.ClassINET,
|
|
||||||
Ttl: uint32(d.config.NodeTTL / time.Second),
|
|
||||||
},
|
|
||||||
A: ip,
|
|
||||||
}
|
|
||||||
msg.Extra = append(msg.Extra, a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preparedQueryLookup is used to handle a prepared query.
|
// preparedQueryLookup is used to handle a prepared query.
|
||||||
func (d *DNSServer) preparedQueryLookup(network, datacenter, query string, req, resp *dns.Msg) {
|
func (d *DNSServer) preparedQueryLookup(network, datacenter, query string, req, resp *dns.Msg) {
|
||||||
// Execute the prepared query.
|
// Execute the prepared query.
|
||||||
|
@ -757,8 +805,7 @@ RPC:
|
||||||
// not a full on server error. We have to use a string compare
|
// not a full on server error. We have to use a string compare
|
||||||
// here since the RPC layer loses the type information.
|
// here since the RPC layer loses the type information.
|
||||||
if err.Error() == consul.ErrQueryNotFound.Error() {
|
if err.Error() == consul.ErrQueryNotFound.Error() {
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
d.addAuthority(resp)
|
|
||||||
resp.SetRcode(req, dns.RcodeNameError)
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -800,8 +847,7 @@ RPC:
|
||||||
|
|
||||||
// If we have no nodes, return not found!
|
// If we have no nodes, return not found!
|
||||||
if len(out.Nodes) == 0 {
|
if len(out.Nodes) == 0 {
|
||||||
d.addSOA(d.domain, resp)
|
d.addSOA(resp)
|
||||||
d.addAuthority(resp)
|
|
||||||
resp.SetRcode(req, dns.RcodeNameError)
|
resp.SetRcode(req, dns.RcodeNameError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -826,8 +872,7 @@ RPC:
|
||||||
|
|
||||||
// If the answer is empty and the response isn't truncated, return not found
|
// If the answer is empty and the response isn't truncated, return not found
|
||||||
if len(resp.Answer) == 0 && !resp.Truncated {
|
if len(resp.Answer) == 0 && !resp.Truncated {
|
||||||
d.addAuthority(resp)
|
d.addSOA(resp)
|
||||||
d.addSOA(d.domain, resp)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue