feature/conn25: add client metrics for dns response rewrite errors and remove
noisy logs Remove most logs in mapDNSResponse() that could potentially be spammed by a misbehaving or abusive DNS client or resolver. Keep logs, and complement with metrics, for failed rewrites, as they likely point to an internal error, e.g. ip pools exhausted. Metrics allow for potential alerting in the future. Updates tailscale/corp#40125 Updates tailscale/corp#40126 Signed-off-by: Michael Ben-Ami <mzb@tailscale.com>
This commit is contained in:
committed by
mzbenami
parent
468a7f4973
commit
c9b5a918ce
+17
-17
@@ -39,6 +39,7 @@ import (
|
|||||||
"tailscale.com/types/key"
|
"tailscale.com/types/key"
|
||||||
"tailscale.com/types/logger"
|
"tailscale.com/types/logger"
|
||||||
"tailscale.com/types/views"
|
"tailscale.com/types/views"
|
||||||
|
"tailscale.com/util/clientmetric"
|
||||||
"tailscale.com/util/dnsname"
|
"tailscale.com/util/dnsname"
|
||||||
"tailscale.com/util/mak"
|
"tailscale.com/util/mak"
|
||||||
"tailscale.com/util/set"
|
"tailscale.com/util/set"
|
||||||
@@ -1089,6 +1090,20 @@ func makeServFail(logf logger.Logf, h dnsmessage.Header, q dnsmessage.Question)
|
|||||||
return bs
|
return bs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// metricDNSResponseRewriteErrorServfail increments servfail returns
|
||||||
|
// on an error rewriting response for an app connector domain.
|
||||||
|
metricDNSResponseRewriteErrorServfail = clientmetric.NewCounter(
|
||||||
|
"conn25_map_dns_response_rewrite_error_servfail",
|
||||||
|
)
|
||||||
|
|
||||||
|
// metricDNSResponseRewriteUnsupportedQuestionTypeErrorServfail increments servfail returns
|
||||||
|
// on an error rewriting empty answers to an unsupported question type for an app connector domain.
|
||||||
|
metricDNSResponseRewriteUnsupportedQuestionTypeErrorServfail = clientmetric.NewCounter(
|
||||||
|
"conn25_map_dns_response_rewrite_unsupported_question_type_error_servfail",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
// mapDNSResponse parses and inspects the DNS response. If the domain
|
// mapDNSResponse parses and inspects the DNS response. If the domain
|
||||||
// is determined to belong to app this node is client for, it assigns addresses
|
// is determined to belong to app this node is client for, it assigns addresses
|
||||||
// for connecting and rewrites the response to contain Magic IPs.
|
// for connecting and rewrites the response to contain Magic IPs.
|
||||||
@@ -1096,12 +1111,10 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
var p dnsmessage.Parser
|
var p dnsmessage.Parser
|
||||||
hdr, err := p.Start(buf)
|
hdr, err := p.Start(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
questions, err := p.AllQuestions()
|
questions, err := p.AllQuestions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return buf
|
return buf
|
||||||
}
|
}
|
||||||
// Any message we are interested in has one question (RFC 9619)
|
// Any message we are interested in has one question (RFC 9619)
|
||||||
@@ -1139,9 +1152,9 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
var answers []dnsResponseRewrite
|
var answers []dnsResponseRewrite
|
||||||
var cnameChain map[dnsname.FQDN]dnsname.FQDN
|
var cnameChain map[dnsname.FQDN]dnsname.FQDN
|
||||||
if question.Type != dnsmessage.TypeA && question.Type != dnsmessage.TypeAAAA {
|
if question.Type != dnsmessage.TypeA && question.Type != dnsmessage.TypeAAAA {
|
||||||
c.logf("mapping dns response for connector domain, unsupported type: %v", question.Type)
|
|
||||||
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
metricDNSResponseRewriteUnsupportedQuestionTypeErrorServfail.Add(1)
|
||||||
c.logf("error writing empty response for unsupported type: %v", err)
|
c.logf("error writing empty response for unsupported type: %v", err)
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
@@ -1153,14 +1166,11 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
// other classes are unsupported, and we checked the question was for ClassINET already
|
// other classes are unsupported, and we checked the question was for ClassINET already
|
||||||
if h.Class != dnsmessage.ClassINET {
|
if h.Class != dnsmessage.ClassINET {
|
||||||
c.logf("unexpected class for connector domain dns response: %v %v", queriedDomain, h.Class)
|
|
||||||
if err := p.SkipAnswer(); err != nil {
|
if err := p.SkipAnswer(); err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1179,17 +1189,14 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
// a.example.com A (some magic IP that is associated with 1.1.1.1)
|
// a.example.com A (some magic IP that is associated with 1.1.1.1)
|
||||||
r, err := p.CNAMEResource()
|
r, err := p.CNAMEResource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
src, err := normalizeDNSName(h.Name.String())
|
src, err := normalizeDNSName(h.Name.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("bad dnsname: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
target, err := normalizeDNSName(r.CNAME.String())
|
target, err := normalizeDNSName(r.CNAME.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("bad dnsname: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
mak.Set(&cnameChain, src, target)
|
mak.Set(&cnameChain, src, target)
|
||||||
@@ -1197,14 +1204,12 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
if h.Type != question.Type {
|
if h.Type != question.Type {
|
||||||
// would not expect a v4 response to a v6 question or vice versa, don't add a rewrite for this.
|
// would not expect a v4 response to a v6 question or vice versa, don't add a rewrite for this.
|
||||||
if err := p.SkipAnswer(); err != nil {
|
if err := p.SkipAnswer(); err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
answerDomain, err := normalizeDNSName(h.Name.String())
|
answerDomain, err := normalizeDNSName(h.Name.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("bad dnsname: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
// If answerDomain is not the same domain as the domain that was queried for,
|
// If answerDomain is not the same domain as the domain that was queried for,
|
||||||
@@ -1229,9 +1234,7 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
d = target
|
d = target
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
c.logf("unexpected domain for connector domain dns response: %v %v", queriedDomain, answerDomain)
|
|
||||||
if err := p.SkipAnswer(); err != nil {
|
if err := p.SkipAnswer(); err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1241,14 +1244,12 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
if h.Type == dnsmessage.TypeA {
|
if h.Type == dnsmessage.TypeA {
|
||||||
r, err := p.AResource()
|
r, err := p.AResource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
dstAddr = netip.AddrFrom4(r.A)
|
dstAddr = netip.AddrFrom4(r.A)
|
||||||
} else {
|
} else {
|
||||||
r, err := p.AAAAResource()
|
r, err := p.AAAAResource()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
dstAddr = netip.AddrFrom16(r.AAAA)
|
dstAddr = netip.AddrFrom16(r.AAAA)
|
||||||
@@ -1256,15 +1257,14 @@ func (c *Conn25) mapDNSResponse(buf []byte) []byte {
|
|||||||
answers = append(answers, dnsResponseRewrite{domain: queriedDomain, dst: dstAddr, ttl: time.Second * time.Duration(h.TTL)})
|
answers = append(answers, dnsResponseRewrite{domain: queriedDomain, dst: dstAddr, ttl: time.Second * time.Duration(h.TTL)})
|
||||||
default:
|
default:
|
||||||
// we already checked the question was for a supported type, this answer is unexpected
|
// we already checked the question was for a supported type, this answer is unexpected
|
||||||
c.logf("unexpected type for connector domain dns response: %v %v", queriedDomain, h.Type)
|
|
||||||
if err := p.SkipAnswer(); err != nil {
|
if err := p.SkipAnswer(); err != nil {
|
||||||
c.logf("error parsing dns response: %v", err)
|
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
newBuf, err := c.client.rewriteDNSResponse(appName, hdr, questions, answers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
metricDNSResponseRewriteErrorServfail.Add(1)
|
||||||
c.logf("error rewriting dns response: %v", err)
|
c.logf("error rewriting dns response: %v", err)
|
||||||
return makeServFail(c.logf, hdr, question)
|
return makeServFail(c.logf, hdr, question)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user