From c8c3d4e48d55e9c25b1142c68c0f905a3509135e Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 22 Dec 2016 13:46:09 +0100 Subject: [PATCH] dnsdist: Handle responses with qdcount == 0 @rygl reported that unbound at least sends `Refused` responses containing only the DNS header. --- pdns/dnsdist-cache.cc | 12 ++++- pdns/dnsdist.cc | 18 ++++++- regression-tests.dnsdist/test_Basics.py | 65 +++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 4 deletions(-) diff --git a/pdns/dnsdist-cache.cc b/pdns/dnsdist-cache.cc index 180c18c65..063aa6114 100644 --- a/pdns/dnsdist-cache.cc +++ b/pdns/dnsdist-cache.cc @@ -166,14 +166,22 @@ bool DNSDistPacketCache::get(const DNSQuestion& dq, uint16_t consumed, uint16_t return false; } + memcpy(response, &queryId, sizeof(queryId)); + memcpy(response + sizeof(queryId), value.value.c_str() + sizeof(queryId), sizeof(dnsheader) - sizeof(queryId)); + + if (value.len == sizeof(dnsheader)) { + /* DNS header only, our work here is done */ + *responseLen = value.len; + d_hits++; + return true; + } + string dnsQName(dq.qname->toDNSString()); const size_t dnsQNameLen = dnsQName.length(); if (value.len < (sizeof(dnsheader) + dnsQNameLen)) { return false; } - memcpy(response, &queryId, sizeof(queryId)); - memcpy(response + sizeof(queryId), value.value.c_str() + sizeof(queryId), sizeof(dnsheader) - sizeof(queryId)); memcpy(response + sizeof(dnsheader), dnsQName.c_str(), dnsQNameLen); if (value.len > (sizeof(dnsheader) + dnsQNameLen)) { memcpy(response + sizeof(dnsheader) + dnsQNameLen, value.value.c_str() + sizeof(dnsheader) + dnsQNameLen, value.len - (sizeof(dnsheader) + dnsQNameLen)); diff --git a/pdns/dnsdist.cc b/pdns/dnsdist.cc index 8c94b65b8..9733359b9 100644 --- a/pdns/dnsdist.cc +++ b/pdns/dnsdist.cc @@ -199,6 +199,16 @@ bool responseContentMatches(const char* response, const uint16_t responseLen, co return false; } + if (dh->qdcount == 0) { + if (dh->rcode != RCode::NoError && dh->rcode != RCode::NXDomain) { + return true; + } + else { + g_stats.nonCompliantResponses++; + return false; + } + } + try { rqname=DNSName(response, responseLen, sizeof(dnsheader), false, &rqtype, &rqclass, &consumed); } @@ -238,6 +248,12 @@ bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, return false; } + restoreFlags(dh, origFlags); + + if (*responseLen == sizeof(dnsheader)) { + return true; + } + if(g_fixupCase) { string realname = qname.toDNSString(); if (*responseLen >= (sizeof(dnsheader) + realname.length())) { @@ -245,8 +261,6 @@ bool fixUpResponse(char** response, uint16_t* responseLen, size_t* responseSize, } } - restoreFlags(dh, origFlags); - if (ednsAdded || ecsAdded) { char * optStart = NULL; size_t optLen = 0; diff --git a/regression-tests.dnsdist/test_Basics.py b/regression-tests.dnsdist/test_Basics.py index 6973dfd78..7049f060f 100644 --- a/regression-tests.dnsdist/test_Basics.py +++ b/regression-tests.dnsdist/test_Basics.py @@ -315,6 +315,71 @@ class TestBasics(DNSDistTest): receivedQuery.id = query.id self.assertEquals(query, receivedQuery) + def testHeaderOnlyRefused(self): + """ + Basics: Header-only refused response + """ + name = 'header-only-refused-response.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + response.set_rcode(dns.rcode.REFUSED) + response.question = [] + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, response) + + def testHeaderOnlyNoErrorResponse(self): + """ + Basics: Header-only NoError response should be dropped + """ + name = 'header-only-noerror-response.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + response.question = [] + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, None) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, None) + + def testHeaderOnlyNXDResponse(self): + """ + Basics: Header-only NXD response should be dropped + """ + name = 'header-only-nxd-response.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + response.set_rcode(dns.rcode.NXDOMAIN) + response.question = [] + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, None) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(receivedResponse, None) + if __name__ == '__main__': unittest.main() -- 2.40.0