libnetwork: write ServFail if DNS reply msg is bad
If the resolver's DNSBackend returns a name that cannot be marshaled into a well-formed DNS message, the resolver will only discover this when it attempts to write the reply message and it fails with an error. No reply message is sent, leaving the client to wait out its timeout and the user in the dark about what went wrong. When writing the intended reply message fails, retry once with a ServFail response to inform the client and user that the DNS query was not resolved due to a problem with to the resolver, not the network. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
parent
1da85f7bdc
commit
5eaf898fcb
2 changed files with 34 additions and 2 deletions
|
@ -378,9 +378,18 @@ func (r *Resolver) serveDNS(w dns.ResponseWriter, query *dns.Msg) {
|
||||||
|
|
||||||
reply := func(msg *dns.Msg) {
|
reply := func(msg *dns.Msg) {
|
||||||
if err = w.WriteMsg(msg); err != nil {
|
if err = w.WriteMsg(msg); err != nil {
|
||||||
r.log(ctx).WithError(err).Errorf("[resolver] failed to write response")
|
r.log(ctx).WithError(err).Error("[resolver] failed to write response")
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
span.SetStatus(codes.Error, "WriteMsg failed")
|
span.SetStatus(codes.Error, "WriteMsg failed")
|
||||||
|
// Make a best-effort attempt to send a failure response to the
|
||||||
|
// client so it doesn't have to wait for a timeout if the failure
|
||||||
|
// has to do with the content of msg rather than the connection.
|
||||||
|
if msg.Rcode != dns.RcodeServerFailure {
|
||||||
|
if err := w.WriteMsg(new(dns.Msg).SetRcode(query, dns.RcodeServerFailure)); err != nil {
|
||||||
|
r.log(ctx).WithError(err).Error("[resolver] writing ServFail response also failed")
|
||||||
|
span.RecordError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -86,7 +86,7 @@ func checkDNSAnswersCount(t *testing.T, m *dns.Msg, expected int) {
|
||||||
func checkDNSResponseCode(t *testing.T, m *dns.Msg, expected int) {
|
func checkDNSResponseCode(t *testing.T, m *dns.Msg, expected int) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if m.MsgHdr.Rcode != expected {
|
if m.MsgHdr.Rcode != expected {
|
||||||
t.Fatalf("Expected DNS response code: %d. Found: %d", expected, m.MsgHdr.Rcode)
|
t.Fatalf("Expected DNS response code: %d (%s). Found: %d (%s)", expected, dns.RcodeToString[expected], m.MsgHdr.Rcode, dns.RcodeToString[m.MsgHdr.Rcode])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,3 +359,26 @@ func TestProxyNXDOMAIN(t *testing.T) {
|
||||||
assert.Assert(t, is.Len(resp.Ns, 1))
|
assert.Assert(t, is.Len(resp.Ns, 1))
|
||||||
assert.Equal(t, resp.Ns[0].String(), mockSOA.String())
|
assert.Equal(t, resp.Ns[0].String(), mockSOA.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ptrDNSBackend struct {
|
||||||
|
noopDNSBackend
|
||||||
|
zone map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ptrDNSBackend) ResolveIP(_ context.Context, name string) string {
|
||||||
|
return b.zone[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regression test for https://github.com/moby/moby/issues/46928
|
||||||
|
func TestInvalidReverseDNS(t *testing.T) {
|
||||||
|
rsv := NewResolver("", false, &ptrDNSBackend{zone: map[string]string{"4.3.2.1": "sixtyfourcharslong9012345678901234567890123456789012345678901234"}})
|
||||||
|
rsv.logger = testLogger(t)
|
||||||
|
|
||||||
|
w := &tstwriter{}
|
||||||
|
q := new(dns.Msg).SetQuestion("4.3.2.1.in-addr.arpa.", dns.TypePTR)
|
||||||
|
rsv.serveDNS(w, q)
|
||||||
|
resp := w.GetResponse()
|
||||||
|
checkNonNullResponse(t, resp)
|
||||||
|
t.Log("Response: ", resp.String())
|
||||||
|
checkDNSResponseCode(t, resp, dns.RcodeServerFailure)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue