]> granicus.if.org Git - pdns/commitdiff
dnsdist: Handle responses with qdcount == 0
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 22 Dec 2016 12:46:09 +0000 (13:46 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 22 Dec 2016 14:49:55 +0000 (15:49 +0100)
@rygl reported that unbound at least sends `Refused` responses
containing only the DNS header.

pdns/dnsdist-cache.cc
pdns/dnsdist.cc
regression-tests.dnsdist/test_Basics.py

index 180c18c6590999293b8903070632c9ca85f754f1..063aa61141df9e5bae1a2c48044e8595f65f8a29 100644 (file)
@@ -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));
index 8c94b65b87d319dfc0bc0ea552b60c9b666c48bb..9733359b9357d43d0fa10da9e2d2ed2285fc3ad3 100644 (file)
@@ -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;
index 6973dfd786ff4cef73389cdcdec41b23a6b3e29e..7049f060fc0f8a800d91fff080a43922ce296424 100644 (file)
@@ -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()