]> granicus.if.org Git - pdns/commitdiff
dnsdist: Filter on opcode, records count/type, trailing data
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 6 Jun 2016 08:34:37 +0000 (10:34 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 6 Jun 2016 08:34:37 +0000 (10:34 +0200)
* Add `OpcodeRule()` to filter on opcode + DNSOpcode.* Lua values
* Add `TrailingDataRule()` to filter queries with trailing data
* Add `RecordsCountRule(section, minCount, maxCount)` to match on
the number of records in a given section
* Add `RecordsTypeCountRule(section, type, minCount, maxCount)` to
match on the number of records of type `type` in a given section
* Add DNSSection.* Lua values
* Add DNSClass.* Lua values

14 files changed:
pdns/README-dnsdist.md
pdns/dnsdist-lua.cc
pdns/dnsdistconf.lua
pdns/dnsparser.cc
pdns/dnsparser.hh
pdns/dnsrulactions.hh
regression-tests.dnsdist/dnsdisttests.py
regression-tests.dnsdist/test_Advanced.py
regression-tests.dnsdist/test_Basics.py
regression-tests.dnsdist/test_Carbon.py
regression-tests.dnsdist/test_CheckConfig.py
regression-tests.dnsdist/test_EdnsClientSubnet.py
regression-tests.dnsdist/test_RecordsCount.py [new file with mode: 0644]
regression-tests.dnsdist/test_Trailing.py [new file with mode: 0644]

index 110d35662306edaf62fc5dafd6edf4b00a9c6022..83cc56d17bf95cd5d085ce4811eb20f6206fba00 100644 (file)
@@ -332,6 +332,10 @@ Rules have selectors and actions. Current selectors are:
  * RE2Rule on query name (optional)
  * Packet requests DNSSEC processing
  * Query received over UDP or TCP
+ * Opcode (OpcodeRule)
+ * Number of entries in a given section (RecordsCountRule)
+ * Number of entries of a specific type in a given section (RecordsTypeCountRule)
+ * Presence of trailing data (TrailingDataRule)
 
 Special rules are:
 
@@ -383,13 +387,17 @@ A DNS rule can be:
  * a MaxQPSRule
  * a NetmaskGroupRule
  * a NotRule
+ * an OpcodeRule
  * an OrRule
  * a QClassRule
  * a QTypeRule
  * a RegexRule
  * a RE2Rule
+ * a RecordsCountRule
+ * a RecordsTypeCountRule
  * a SuffixMatchNodeRule
  * a TCPRule
+ * a TrailingDataRule
 
 Some specific actions do not stop the processing when they match, contrary to all other actions:
 
@@ -1072,11 +1080,16 @@ instantiate a server with additional parameters
     * `NetmaskGroupRule()`: matches traffic from the specified network range
     * `NotRule()`: matches if the sub-rule does not match
     * `OrRule()`: matches if at least one of the sub-rules matches
+    * `OpcodeRule()`: matches queries with the specified opcode
     * `QClassRule(qclass)`: matches queries with the specified qclass (numeric)
     * `QTypeRule(qtype)`: matches queries with the specified qtype
     * `RegexRule(regex)`: matches the query name against the supplied regex
+    * `RecordsCountRule(section, minCount, maxCount)`: matches if there is at least `minCount` and at most `maxCount` records in the `section` section
+    * `RecordsTypeCountRule(section, type, minCount, maxCount)`: matches if there is at least `minCount` and at most `maxCount` records of type `type` in the `section` section
+    * `RE2Rule(regex)`: matches the query name against the supplied regex using the RE2 engine
     * `SuffixMatchNodeRule(smn, [quiet-bool])`: matches based on a group of domain suffixes for rapid testing of membership. Pass `true` as second parameter to prevent listing of all domains matched.
     * `TCPRule(tcp)`: matches question received over TCP if `tcp` is true, over UDP otherwise
+    * `TrailingDataRule()`: matches if the query has trailing data
  * Rule management related:
     * `getAction(num)`: returns the Action associate with rule 'num'.
     * `showRules()`: show all defined rules (Pool, Block, QPS, addAnyTCRule)
@@ -1192,6 +1205,7 @@ instantiate a server with additional parameters
         * member `dh`: DNSHeader
         * member `len`: the question length
         * member `localaddr`: ComboAddress of the local bind this question was received on
+        * member `opcode`: the question opcode
         * member `qname`: DNSName of this question
         * member `qclass`: QClass (as an unsigned integer) of this question
         * member `qtype`: QType (as an unsigned integer) of this question
index 6154918159f9cc760881afe1fa5472c70cab6880..45b0ef0714b63247584b28809b9a419fda1d2c78 100644 (file)
@@ -138,13 +138,49 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       {"Delay", (int)DNSAction::Action::Delay}}
     );
 
-    g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
+  g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
       {"None",(int)DNSResponseAction::Action::None}}
     );
 
+  g_lua.writeVariable("DNSClass", std::unordered_map<string,int>{
+      {"IN",    QClass::IN    },
+      {"CHAOS", QClass::CHAOS },
+      {"NONE",  QClass::NONE  },
+      {"ANY",   QClass::ANY   }
+    });
+
+  g_lua.writeVariable("DNSOpcode", std::unordered_map<string,int>{
+      {"Query",  Opcode::Query  },
+      {"IQuery", Opcode::IQuery },
+      {"Status", Opcode::Status },
+      {"Notify", Opcode::Notify },
+      {"Update", Opcode::Update }
+    });
+
+  g_lua.writeVariable("DNSSection", std::unordered_map<string,int>{
+      {"Question",  0 },
+      {"Answer",    1 },
+      {"Authority", 2 },
+      {"Additional",3 }
+    });
+
+  vector<pair<string, int> > rcodes = {{"NOERROR",  RCode::NoError  },
+                                       {"FORMERR",  RCode::FormErr  },
+                                       {"SERVFAIL", RCode::ServFail },
+                                       {"NXDOMAIN", RCode::NXDomain },
+                                       {"NOTIMP",   RCode::NotImp   },
+                                       {"REFUSED",  RCode::Refused  },
+                                       {"YXDOMAIN", RCode::YXDomain },
+                                       {"YXRRSET",  RCode::YXRRSet  },
+                                       {"NXRRSET",  RCode::NXRRSet  },
+                                       {"NOTAUTH",  RCode::NotAuth  },
+                                       {"NOTZONE",  RCode::NotZone  }
+  };
   vector<pair<string, int> > dd;
   for(const auto& n : QType::names)
     dd.push_back({n.first, n.second});
+  for(const auto& n : rcodes)
+    dd.push_back({n.first, n.second});
   g_lua.writeVariable("dnsdist", dd);
   
   g_lua.writeFunction("newServer", 
@@ -778,10 +814,13 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       }
       return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
     });
-    g_lua.writeFunction("QClassRule", [](int c) {
+  g_lua.writeFunction("QClassRule", [](int c) {
       return std::shared_ptr<DNSRule>(new QClassRule(c));
     });
 
+  g_lua.writeFunction("OpcodeRule", [](uint8_t code) {
+      return std::shared_ptr<DNSRule>(new OpcodeRule(code));
+    });
 
   g_lua.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
       return std::shared_ptr<DNSRule>(new AndRule(a));
@@ -803,7 +842,19 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
       return std::shared_ptr<DNSRule>(new NotRule(rule));
     });
 
-  g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea) 
+  g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
+      return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
+    });
+
+  g_lua.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) {
+      return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
+    });
+
+  g_lua.writeFunction("TrailingDataRule", []() {
+      return std::shared_ptr<DNSRule>(new TrailingDataRule());
+    });
+
+  g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea)
                      {
                         setLuaSideEffect();
                        auto rule=makeRule(var);
@@ -1310,6 +1361,7 @@ vector<std::function<void(void)>> setupLua(bool client, const std::string& confi
   /* DNSDist DNSQuestion */
   g_lua.registerMember("dh", &DNSQuestion::dh);
   g_lua.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
+  g_lua.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
   g_lua.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
   g_lua.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
   g_lua.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
index be86a0477586746d3fe56b4275378dea3be1bc13..5b9bedd016cceb6c59ceb6cacea8fa981040a7f2 100644 (file)
@@ -1,4 +1,3 @@
-
 -- listen for console connection with the given secret key
 controlSocket("0.0.0.0")
 setKey("MXNeLFWHUe4363BBKrY06cAsH8NWNb+Se2eXU5+Bb74=")
@@ -167,10 +166,10 @@ end
 -- addAction(AndRule({QTypeRule("ANY"), TCPRule(true)}), TCAction())
 
 -- return 'not implemented' for qtype != A over UDP
--- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(4))
+-- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(dnsdist.NOTIMPL))
 
 -- return 'not implemented' for qtype == A OR received over UDP
--- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(4))
+-- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(dnsdist.NOTIMPL))
 
 -- log all queries to a 'dndist.log' file, in text-mode (not binary)
 -- addAction(AllRule(), LogAction("dnsdist.log", false))
@@ -180,9 +179,16 @@ end
 
 -- drop all queries for the CHAOS class
 -- addAction(QClassRule(3), DropAction())
+-- addAction(QClassRule(DNSClass.CHAOS), DropAction())
+
+-- drop all queries with the UPDATE opcode
+-- addAction(OpcodeRule(DNSOpcode.Update), DropAction())
+
+-- refuse all queries not having exactly one question
+-- addAction(NotRule(RecordsCountRule(DNSSection.Question, 1, 1)), RCodeAction(dnsdist.REFUSED))
 
 -- return 'refused' for domains matching the regex evil[0-9]{4,}.powerdns.com$
--- addAction(RegexRule("evil[0-9]{4,}\\.powerdns\\.com$"), RCodeAction(5))
+-- addAction(RegexRule("evil[0-9]{4,}\\.powerdns\\.com$"), RCodeAction(dnsdist.REFUSED))
 
 -- spoof responses for A, AAAA and ANY for spoof.powerdns.com.
 -- A queries will get 192.0.2.1, AAAA 2001:DB8::1 and ANY both
index 493d82e88e0ab2805c65e0d7934aac891c39ddbe..e47721d708cab90db5e61aa24111f43b1daed013 100644 (file)
@@ -617,6 +617,10 @@ public:
     tmp = htonl(tmp);
     memcpy(d_packet + d_offset-4, (const char*)&tmp, sizeof(tmp));
   }
+  uint32_t getOffset() const
+  {
+    return d_offset;
+  }
 private:
   void moveOffset(uint16_t by)
   {
@@ -713,3 +717,112 @@ uint32_t getDNSPacketMinTTL(const char* packet, size_t length)
   }
   return result;
 }
+
+uint32_t getDNSPacketLength(const char* packet, size_t length)
+{
+  uint32_t result = length;
+  if(length < sizeof(dnsheader)) {
+    return result;
+  }
+  try
+  {
+    const dnsheader* dh = (const dnsheader*) packet;
+    DNSPacketMangler dpm(const_cast<char*>(packet), length);
+
+    const uint16_t qdcount = ntohs(dh->qdcount);
+    for(size_t n = 0; n < qdcount; ++n) {
+      dpm.skipLabel();
+      dpm.skipBytes(4); // qtype, qclass
+    }
+    const size_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
+    for(size_t n = 0; n < numrecords; ++n) {
+      dpm.skipLabel();
+
+      /* const uint16_t dnstype */ dpm.get16BitInt();
+      /* uint16_t dnsclass */ dpm.get16BitInt();
+      /* const uint32_t ttl */ dpm.get32BitInt();
+      dpm.skipRData();
+    }
+    result = dpm.getOffset();
+  }
+  catch(...)
+  {
+  }
+  return result;
+}
+
+uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type)
+{
+  uint16_t result = 0;
+  if(length < sizeof(dnsheader)) {
+    return result;
+  }
+  try
+  {
+    const dnsheader* dh = (const dnsheader*) packet;
+    DNSPacketMangler dpm(const_cast<char*>(packet), length);
+
+    const uint16_t qdcount = ntohs(dh->qdcount);
+    for(size_t n = 0; n < qdcount; ++n) {
+      dpm.skipLabel();
+      if (section == 0) {
+        uint16_t dnstype = dpm.get16BitInt();
+        if (dnstype == type) {
+          result++;
+        }
+        dpm.skipBytes(2); // qclass
+      } else {
+        dpm.skipBytes(4); // qtype, qclass
+      }
+    }
+    const uint16_t ancount = ntohs(dh->ancount);
+    for(size_t n = 0; n < ancount; ++n) {
+      dpm.skipLabel();
+      if (section == 1) {
+        uint16_t dnstype = dpm.get16BitInt();
+        if (dnstype == type) {
+          result++;
+        }
+        dpm.skipBytes(2); // qclass
+      } else {
+        dpm.skipBytes(4); // qtype, qclass
+      }
+      /* const uint32_t ttl */ dpm.get32BitInt();
+      dpm.skipRData();
+    }
+    const uint16_t nscount = ntohs(dh->nscount);
+    for(size_t n = 0; n < nscount; ++n) {
+      dpm.skipLabel();
+      if (section == 2) {
+        uint16_t dnstype = dpm.get16BitInt();
+        if (dnstype == type) {
+          result++;
+        }
+        dpm.skipBytes(2); // qclass
+      } else {
+        dpm.skipBytes(4); // qtype, qclass
+      }
+      /* const uint32_t ttl */ dpm.get32BitInt();
+      dpm.skipRData();
+    }
+    const uint16_t arcount = ntohs(dh->arcount);
+    for(size_t n = 0; n < arcount; ++n) {
+      dpm.skipLabel();
+      if (section == 3) {
+        uint16_t dnstype = dpm.get16BitInt();
+        if (dnstype == type) {
+          result++;
+        }
+        dpm.skipBytes(2); // qclass
+      } else {
+        dpm.skipBytes(4); // qtype, qclass
+      }
+      /* const uint32_t ttl */ dpm.get32BitInt();
+      dpm.skipRData();
+    }
+  }
+  catch(...)
+  {
+  }
+  return result;
+}
index c54b456f91f064782a0a71d1c05569c433a7f8e5..05117bf5dc36648d72ea51ddc1c54bc310fb2c76 100644 (file)
@@ -381,6 +381,8 @@ string simpleCompress(const string& label, const string& root="");
 void ageDNSPacket(char* packet, size_t length, uint32_t seconds);
 void ageDNSPacket(std::string& packet, uint32_t seconds);
 uint32_t getDNSPacketMinTTL(const char* packet, size_t length);
+uint32_t getDNSPacketLength(const char* packet, size_t length);
+uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type);
 
 template<typename T>
 std::shared_ptr<T> getRR(const DNSRecord& dr)
index 5f60730d22ac293dc88efed36213890f1fc583a2..899e7d64b474b9f12f3738a9d9f7cd126e31cbf8 100644 (file)
@@ -6,6 +6,7 @@
 #include "lock.hh"
 #include "remote_logger.hh"
 #include "dnsdist-protobuf.hh"
+#include "dnsparser.hh"
 
 class MaxQPSIPRule : public DNSRule
 {
@@ -310,6 +311,23 @@ private:
   uint16_t d_qclass;
 };
 
+class OpcodeRule : public DNSRule
+{
+public:
+  OpcodeRule(uint8_t opcode) : d_opcode(opcode)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    return d_opcode == dq->dh->opcode;
+  }
+  string toString() const override
+  {
+    return "opcode=="+d_opcode;
+  }
+private:
+  uint8_t d_opcode;
+};
 
 class TCPRule : public DNSRule
 {
@@ -348,6 +366,127 @@ private:
   shared_ptr<DNSRule> d_rule;
 };
 
+class RecordsCountRule : public DNSRule
+{
+public:
+  RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount): d_minCount(minCount), d_maxCount(maxCount), d_section(section)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    uint16_t count = 0;
+    switch(d_section) {
+    case 0:
+      count = ntohs(dq->dh->qdcount);
+      break;
+    case 1:
+      count = ntohs(dq->dh->ancount);
+      break;
+    case 2:
+      count = ntohs(dq->dh->nscount);
+      break;
+    case 3:
+      count = ntohs(dq->dh->arcount);
+      break;
+    }
+    return count >= d_minCount && count <= d_maxCount;
+  }
+  string toString() const override
+  {
+    string section;
+    switch(d_section) {
+    case 0:
+      section = "QD";
+      break;
+    case 1:
+      section = "AN";
+      break;
+    case 2:
+      section = "NS";
+      break;
+    case 3:
+      section = "AR";
+      break;
+    }
+    return std::to_string(d_minCount) + " <= records in " + section + " <= "+ std::to_string(d_maxCount);
+  }
+private:
+  uint16_t d_minCount;
+  uint16_t d_maxCount;
+  uint8_t d_section;
+};
+
+class RecordsTypeCountRule : public DNSRule
+{
+public:
+  RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount): d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section)
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    uint16_t count = 0;
+    switch(d_section) {
+    case 0:
+      count = ntohs(dq->dh->qdcount);
+      break;
+    case 1:
+      count = ntohs(dq->dh->ancount);
+      break;
+    case 2:
+      count = ntohs(dq->dh->nscount);
+      break;
+    case 3:
+      count = ntohs(dq->dh->arcount);
+      break;
+    }
+    if (count < d_minCount || count > d_maxCount) {
+      return false;
+    }
+    count = getRecordsOfTypeCount(reinterpret_cast<const char*>(dq->dh), dq->len, d_section, d_type);
+    return count >= d_minCount && count <= d_maxCount;
+  }
+  string toString() const override
+  {
+    string section;
+    switch(d_section) {
+    case 0:
+      section = "QD";
+      break;
+    case 1:
+      section = "AN";
+      break;
+    case 2:
+      section = "NS";
+      break;
+    case 3:
+      section = "AR";
+      break;
+    }
+    return std::to_string(d_minCount) + " <= " + QType(d_type).getName() + " records in " + section + " <= "+ std::to_string(d_maxCount);
+  }
+private:
+  uint16_t d_type;
+  uint16_t d_minCount;
+  uint16_t d_maxCount;
+  uint8_t d_section;
+};
+
+class TrailingDataRule : public DNSRule
+{
+public:
+  TrailingDataRule()
+  {
+  }
+  bool matches(const DNSQuestion* dq) const override
+  {
+    uint16_t length = getDNSPacketLength(reinterpret_cast<const char*>(dq->dh), dq->len);
+    return length < dq->len;
+  }
+  string toString() const override
+  {
+    return "trailing data";
+  }
+};
 
 class DropAction : public DNSAction
 {
index a9ef2be97ef83a156eac2e7731ada18c33b5c7ea..8184eed4a0e799a45edc89e4737572f5567f983c 100644 (file)
@@ -144,14 +144,15 @@ class DNSDistTest(unittest.TestCase):
         return response
 
     @classmethod
-    def UDPResponder(cls, port):
+    def UDPResponder(cls, port, ignoreTrailing=False):
         sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         sock.bind(("127.0.0.1", port))
         while True:
             data, addr = sock.recvfrom(4096)
-            request = dns.message.from_wire(data)
+            request = dns.message.from_wire(data, ignore_trailing=ignoreTrailing)
             response = cls._getResponse(request)
+
             if not response:
                 continue
 
@@ -161,7 +162,7 @@ class DNSDistTest(unittest.TestCase):
         sock.close()
 
     @classmethod
-    def TCPResponder(cls, port):
+    def TCPResponder(cls, port, ignoreTrailing=False):
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
         try:
@@ -177,8 +178,9 @@ class DNSDistTest(unittest.TestCase):
             data = conn.recv(2)
             (datalen,) = struct.unpack("!H", data)
             data = conn.recv(datalen)
-            request = dns.message.from_wire(data)
+            request = dns.message.from_wire(data, ignore_trailing=ignoreTrailing)
             response = cls._getResponse(request)
+
             if not response:
                 continue
 
@@ -189,7 +191,7 @@ class DNSDistTest(unittest.TestCase):
         sock.close()
 
     @classmethod
-    def sendUDPQuery(cls, query, response, useQueue=True, timeout=2.0):
+    def sendUDPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
         if useQueue:
             cls._toResponderQueue.put(response, True, timeout)
 
@@ -197,7 +199,9 @@ class DNSDistTest(unittest.TestCase):
             cls._sock.settimeout(timeout)
 
         try:
-            cls._sock.send(query.to_wire())
+            if not rawQuery:
+                query = query.to_wire()
+            cls._sock.send(query)
             data = cls._sock.recv(4096)
         except socket.timeout:
             data = None
@@ -214,7 +218,7 @@ class DNSDistTest(unittest.TestCase):
         return (receivedQuery, message)
 
     @classmethod
-    def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0):
+    def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
         if useQueue:
             cls._toResponderQueue.put(response, True, timeout)
         sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -224,7 +228,11 @@ class DNSDistTest(unittest.TestCase):
         sock.connect(("127.0.0.1", cls._dnsDistPort))
 
         try:
-            wire = query.to_wire()
+            if not rawQuery:
+                wire = query.to_wire()
+            else:
+                wire = query
+
             sock.send(struct.pack("!H", len(wire)))
             sock.send(wire)
             data = sock.recv(2)
index bd2e7849feb7bed82aa104972a8d94849b0aab57..e2542b3bfa5f0a7ac409dcd75d035da6bd7f4013 100644 (file)
@@ -502,7 +502,7 @@ class TestAdvancedTruncateAnyAndTCP(DNSDistTest):
 class TestAdvancedAndNot(DNSDistTest):
 
     _config_template = """
-    addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(4))
+    addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(dnsdist.NOTIMP))
     newServer{address="127.0.0.1:%s"}
     """
     def testAOverUDPReturnsNotImplementedCanary(self):
@@ -574,7 +574,7 @@ class TestAdvancedAndNot(DNSDistTest):
 class TestAdvancedOr(DNSDistTest):
 
     _config_template = """
-    addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(4))
+    addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(dnsdist.NOTIMP))
     newServer{address="127.0.0.1:%s"}
     """
     def testAAAAOverUDPReturnsNotImplemented(self):
@@ -709,7 +709,7 @@ class TestAdvancedQClass(DNSDistTest):
 
     _config_template = """
     newServer{address="127.0.0.1:%s"}
-    addAction(QClassRule(3), DropAction())
+    addAction(QClassRule(DNSClass.CHAOS), DropAction())
     """
     def testAdvancedQClassChaosDrop(self):
         """
@@ -718,17 +718,10 @@ class TestAdvancedQClass(DNSDistTest):
         """
         name = 'qclasschaos.advanced.tests.powerdns.com.'
         query = dns.message.make_query(name, 'TXT', 'CHAOS')
-        response = dns.message.make_response(query)
-        rrset = dns.rrset.from_text(name,
-                                    3600,
-                                    dns.rdataclass.CH,
-                                    dns.rdatatype.TXT,
-                                    'hop')
-        response.answer.append(rrset)
 
-        (_, receivedResponse) = self.sendUDPQuery(query, response)
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None)
         self.assertEquals(receivedResponse, None)
-        (_, receivedResponse) = self.sendTCPQuery(query, response)
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None)
         self.assertEquals(receivedResponse, None)
 
     def testAdvancedQClassINAllow(self):
@@ -760,6 +753,55 @@ class TestAdvancedQClass(DNSDistTest):
         self.assertEquals(query, receivedQuery)
         self.assertEquals(response, receivedResponse)
 
+class TestAdvancedOpcode(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addAction(OpcodeRule(DNSOpcode.Notify), DropAction())
+    """
+    def testAdvancedOpcodeNotifyDrop(self):
+        """
+        Advanced: Drop Opcode NOTIFY
+
+        """
+        name = 'opcodenotify.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.set_opcode(dns.opcode.NOTIFY)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None)
+        self.assertEquals(receivedResponse, None)
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None)
+        self.assertEquals(receivedResponse, None)
+
+    def testAdvancedOpcodeUpdateINAllow(self):
+        """
+        Advanced: Allow Opcode UPDATE
+
+        """
+        name = 'opcodeupdate.advanced.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        query.set_opcode(dns.opcode.UPDATE)
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
 
 class TestAdvancedNonTerminalRule(DNSDistTest):
 
@@ -857,7 +899,6 @@ class TestAdvancedRestoreFlagsOnSelfResponse(DNSDistTest):
         query = dns.message.make_query(name, 'A', 'IN')
         # dnsdist set RA = RD for spoofed responses
         query.flags &= ~dns.flags.RD
-        expectedQuery = dns.message.make_query(name, 'A', 'IN')
 
         response = dns.message.make_response(query)
         rrset = dns.rrset.from_text(name,
@@ -927,7 +968,7 @@ class TestAdvancedQPSNone(DNSDistTest):
 
     _config_template = """
     addQPSLimit("qpsnone.advanced.tests.powerdns.com", 100)
-    addAction(AllRule(), RCodeAction(5))
+    addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -955,7 +996,7 @@ class TestAdvancedNMGRule(DNSDistTest):
     _config_template = """
     allowed = newNMG()
     allowed:addMask("192.0.2.1/32")
-    addAction(NotRule(NetmaskGroupRule(allowed)), RCodeAction(5))
+    addAction(NotRule(NetmaskGroupRule(allowed)), RCodeAction(dnsdist.REFUSED))
     newServer{address="127.0.0.1:%s"}
     """
 
@@ -976,3 +1017,4 @@ class TestAdvancedNMGRule(DNSDistTest):
 
         (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
         self.assertEquals(receivedResponse, expectedResponse)
+
index 5dcc6fb79c097b7da60b0e64561356acd78c48bd..6973dfd786ff4cef73389cdcdec41b23a6b3e29e 100644 (file)
@@ -10,10 +10,10 @@ class TestBasics(DNSDistTest):
     newServer{address="127.0.0.1:%s"}
     truncateTC(true)
     addAnyTCRule()
-    addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(5))
+    addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(dnsdist.REFUSED))
     mySMN = newSuffixMatchNode()
     mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
-    addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(4))
+    addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(dnsdist.NOTIMP))
     addAction(makeRule("drop.test.powerdns.com."), DropAction())
     block=newDNSName("powerdns.org.")
     function blockFilter(dq)
index c3634333d7feb6e93e333ea06f4623deeeae85c2..09b13daf3b052bcc40f32324e5426c982f201f3b 100644 (file)
@@ -2,9 +2,8 @@
 import Queue
 import threading
 import socket
+import sys
 import time
-import unittest
-import dns
 from dnsdisttests import DNSDistTest
 
 class TestCarbon(DNSDistTest):
index eaed2bc84f4a83ffeaa431782f9381dc0858d5a2..709a0ceaa9a3ac72864bfbf7616f0ecb6d121dc2 100644 (file)
@@ -2,7 +2,6 @@
 import unittest
 import os
 import subprocess
-import sys
 import time
 
 class TestCheckConfig(unittest.TestCase):
@@ -35,10 +34,10 @@ class TestCheckConfig(unittest.TestCase):
             newServer{address="127.0.0.1:53"}
             truncateTC(true)
             addAnyTCRule()
-            addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(5))
+            addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(dnsdist.REFUSED))
             mySMN = newSuffixMatchNode()
             mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
-            addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(4))
+            addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(dnsdist.NOTIMP))
             addAction(makeRule("drop.test.powerdns.com."), DropAction())
             block=newDNSName("powerdns.org.")
             function blockFilter(dq)
index 959d4996b665aa856b583a16471ad426740d88ca..273a7dee560f69481a9dc32efcd54bd6b0f5fe6a 100644 (file)
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-import unittest
 import dns
 import clientsubnetoption
 import cookiesoption
diff --git a/regression-tests.dnsdist/test_RecordsCount.py b/regression-tests.dnsdist/test_RecordsCount.py
new file mode 100644 (file)
index 0000000..1193fdd
--- /dev/null
@@ -0,0 +1,286 @@
+#!/usr/bin/env python
+import copy
+import os
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestRecordsCountOnlyOneAR(DNSDistTest):
+
+    _config_template = """
+    addAction(NotRule(RecordsCountRule(DNSSection.Additional, 1, 1)), RCodeAction(dnsdist.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testRecordsCountRefuseEmptyAR(self):
+        """
+        RecordsCount: Refuse arcount == 0
+
+        Send a query to "refuseemptyar.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refuseemptyar.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+    def testRecordsCountAllowOneAR(self):
+        """
+        RecordsCount: Allow arcount == 1
+
+        Send a query to "allowonear.recordscount.tests.powerdns.com.",
+        check that we are getting a valid response.
+        """
+        name = 'allowonear.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        response = dns.message.make_response(query)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   3600,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '127.0.0.1'))
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+    def testRecordsCountRefuseTwoAR(self):
+        """
+        RecordsCount: Refuse arcount > 1
+
+        Send a query to "refusetwoar.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refusetwoar.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        query.additional.append(dns.rrset.from_text(name,
+                                                    3600,
+                                                    dns.rdataclass.IN,
+                                                    dns.rdatatype.A,
+                                                    '127.0.0.1'))
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
+
+    _config_template = """
+    addAction(RecordsCountRule(DNSSection.Answer, 2, 3), AllowAction())
+    addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testRecordsCountRefuseOneAN(self):
+        """
+        RecordsCount: Refuse ancount == 0
+
+        Send a query to "refusenoan.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refusenoan.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+    def testRecordsCountAllowTwoAN(self):
+        """
+        RecordsCount: Allow ancount == 2
+
+        Send a query to "allowtwoan.recordscount.tests.powerdns.com.",
+        check that we are getting a valid response.
+        """
+        name = 'allowtwoan.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        rrset = dns.rrset.from_text_list(name,
+                                         3600,
+                                         dns.rdataclass.IN,
+                                         dns.rdatatype.A,
+                                         ['127.0.0.1', '127.0.0.2'])
+        query.answer.append(rrset)
+        response = dns.message.make_response(query)
+        response.answer.append(rrset)
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+    def testRecordsCountRefuseFourAN(self):
+        """
+        RecordsCount: Refuse ancount > 3
+
+        Send a query to "refusefouran.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refusefouran.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        rrset = dns.rrset.from_text_list(name,
+                                         3600,
+                                         dns.rdataclass.IN,
+                                         dns.rdatatype.A,
+                                         ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'])
+        query.answer.append(rrset)
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+        expectedResponse.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+class TestRecordsCountNothingInNS(DNSDistTest):
+
+    _config_template = """
+    addAction(RecordsCountRule(DNSSection.Authority, 0, 0), AllowAction())
+    addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testRecordsCountRefuseNS(self):
+        """
+        RecordsCount: Refuse nscount != 0
+
+        Send a query to "refusens.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refusens.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.NS,
+                                    'ns.tests.powerdns.com.')
+        query.authority.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+        expectedResponse.authority.append(rrset)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+
+    def testRecordsCountAllowEmptyNS(self):
+        """
+        RecordsCount: Allow nscount == 0
+
+        Send a query to "allowns.recordscount.tests.powerdns.com.",
+        check that we are getting a valid response.
+        """
+        name = 'allowns.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   3600,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '127.0.0.1'))
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+class TestRecordsCountNoOPTInAR(DNSDistTest):
+
+    _config_template = """
+    addAction(NotRule(RecordsTypeCountRule(DNSSection.Additional, dnsdist.OPT, 0, 0)), RCodeAction(dnsdist.REFUSED))
+    newServer{address="127.0.0.1:%s"}
+    """
+
+    def testRecordsCountRefuseOPTinAR(self):
+        """
+        RecordsTypeCount: Refuse OPT in AR
+
+        Send a query to "refuseoptinar.recordscount.tests.powerdns.com.",
+        check that we are getting a REFUSED response.
+        """
+        name = 'refuseoptinar.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+        (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+        (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+        self.assertEquals(receivedResponse, expectedResponse)
+
+    def testRecordsCountAllowNoOPTInAR(self):
+        """
+        RecordsTypeCount: Allow no OPT in AR
+
+        Send a query to "allownooptinar.recordscount.tests.powerdns.com.",
+        check that we are getting a valid response.
+        """
+        name = 'allowwnooptinar.recordscount.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        response.answer.append(dns.rrset.from_text(name,
+                                                   3600,
+                                                   dns.rdataclass.IN,
+                                                   dns.rdatatype.A,
+                                                   '127.0.0.1'))
+
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
diff --git a/regression-tests.dnsdist/test_Trailing.py b/regression-tests.dnsdist/test_Trailing.py
new file mode 100644 (file)
index 0000000..c874ede
--- /dev/null
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+import threading
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestTrailing(DNSDistTest):
+
+    # this test suite uses a different responder port
+    # because, contrary to the other ones, its
+    # responders allow trailing data and we don't want
+    # to mix things up.
+    _testServerPort = 5360
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    addAction(AndRule({QTypeRule(dnsdist.AAAA), TrailingDataRule()}), DropAction())
+    """
+    @classmethod
+    def startResponders(cls):
+        print("Launching responders..")
+
+        cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, True])
+        cls._UDPResponder.setDaemon(True)
+        cls._UDPResponder.start()
+        cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, True])
+        cls._TCPResponder.setDaemon(True)
+        cls._TCPResponder.start()
+
+    def testTrailingAllowed(self):
+        """
+        Trailing: Allowed
+
+        """
+        name = 'allowed.trailing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'A', 'IN')
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    3600,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        response.answer.append(rrset)
+
+        raw = query.to_wire()
+        raw = raw + 'A'* 20
+        (receivedQuery, receivedResponse) = self.sendUDPQuery(raw, response, rawQuery=True)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+        (receivedQuery, receivedResponse) = self.sendTCPQuery(raw, response, rawQuery=True)
+        self.assertTrue(receivedQuery)
+        self.assertTrue(receivedResponse)
+        receivedQuery.id = query.id
+        self.assertEquals(query, receivedQuery)
+        self.assertEquals(response, receivedResponse)
+
+    def testTrailingDropped(self):
+        """
+        Trailing: dropped
+
+        """
+        name = 'dropped.trailing.tests.powerdns.com.'
+        query = dns.message.make_query(name, 'AAAA', 'IN')
+
+        raw = query.to_wire()
+        raw = raw + 'A'* 20
+
+        (_, receivedResponse) = self.sendUDPQuery(raw, response=None, rawQuery=True)
+        self.assertEquals(receivedResponse, None)
+        (_, receivedResponse) = self.sendTCPQuery(raw, response=None, rawQuery=True)
+        self.assertEquals(receivedResponse, None)