]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Add Lua bindings, regression tests for Extended DNS Errors
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 12 Nov 2020 13:53:47 +0000 (14:53 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 18 Nov 2020 09:08:40 +0000 (10:08 +0100)
pdns/lua-recursor4-ffi.hh
pdns/lua-recursor4.cc
pdns/lua-recursor4.hh
pdns/pdns_recursor.cc
pdns/recursordist/test-ednsoptions_cc.cc
regression-tests.recursor-dnssec/cookiesoption.py [changed from file to symlink]
regression-tests.recursor-dnssec/extendederrors.py [new file with mode: 0644]
regression-tests.recursor-dnssec/test_ExtendedErrors.py [new file with mode: 0644]

index 7051b7c549d51b417c044758c192947c157063ef..3fd240904b670dd568c9559f00d7c5f209c43dfb 100644 (file)
@@ -76,6 +76,9 @@ extern "C" {
   void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__ ((visibility ("default")));
   void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow) __attribute__ ((visibility ("default")));
 
+  void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__ ((visibility ("default")));
+  void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra) __attribute__ ((visibility ("default")));
+
   /* returns true if the record was correctly added, false if something went wrong.
      Passing a NULL pointer to 'name' will result in the qname being used for the record owner name. */
   bool pdns_ffi_param_add_record(pdns_ffi_param_t *ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place) __attribute__ ((visibility ("default")));
index f53d4a86fd49f39ea862ca54e1ea8fbf49c0e2dc..b86e30f1857efc30a0a79870f422cbd1db03e25f 100644 (file)
@@ -173,11 +173,34 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerMember("followupPrefix", &DNSQuestion::followupPrefix);
   d_lw->registerMember("followupName", &DNSQuestion::followupName);
   d_lw->registerMember("data", &DNSQuestion::data);
+  d_lw->registerMember<uint16_t (DNSQuestion::*)>("extendedErrorCode", [](const DNSQuestion& dq) -> uint16_t {
+      if (dq.extendedErrorCode && *dq.extendedErrorCode) {
+        return *(*dq.extendedErrorCode);
+      }
+      return 0;
+    },
+    [](DNSQuestion& dq, uint16_t newCode) {
+      if (dq.extendedErrorCode) {
+        *dq.extendedErrorCode = newCode;
+      }
+    });
+  d_lw->registerMember<std::string (DNSQuestion::*)>("extendedErrorExtra", [](const DNSQuestion& dq) -> std::string {
+      if (dq.extendedErrorExtra) {
+        return *dq.extendedErrorExtra;
+      }
+      return "";
+    },
+    [](DNSQuestion& dq, const std::string& newExtra) {
+      if (dq.extendedErrorExtra) {
+        *dq.extendedErrorExtra = newExtra;
+      }
+    });
   d_lw->registerMember("udpQuery", &DNSQuestion::udpQuery);
   d_lw->registerMember("udpAnswer", &DNSQuestion::udpAnswer);
   d_lw->registerMember("udpQueryDest", &DNSQuestion::udpQueryDest);
   d_lw->registerMember("udpCallback", &DNSQuestion::udpCallback);
   d_lw->registerMember("appliedPolicy", &DNSQuestion::appliedPolicy);
+
   d_lw->registerMember<DNSFilterEngine::Policy, std::string>("policyName",
     [](const DNSFilterEngine::Policy& pol) -> std::string {
       return pol.getName();
@@ -219,6 +242,7 @@ void RecursorLua4::postPrepareContext()
   d_lw->registerFunction("getProxyProtocolValues", &DNSQuestion::getProxyProtocolValues);
   d_lw->registerFunction("getEDNSFlags", &DNSQuestion::getEDNSFlags);
   d_lw->registerFunction("getEDNSFlag", &DNSQuestion::getEDNSFlag);
+  
   d_lw->registerMember("name", &DNSRecord::d_name);
   d_lw->registerMember("type", &DNSRecord::d_type);
   d_lw->registerMember("ttl", &DNSRecord::d_ttl);
@@ -578,7 +602,7 @@ unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& edn
 struct pdns_ffi_param
 {
 public:
-  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::unordered_set<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector<ProxyProtocolValue>& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), rcode(rcode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
+  pdns_ffi_param(const DNSName& qname_, uint16_t qtype_, const ComboAddress& local_, const ComboAddress& remote_, const Netmask& ednssubnet_, std::unordered_set<std::string>& policyTags_, std::vector<DNSRecord>& records_, const EDNSOptionViewMap& ednsOptions_, const std::vector<ProxyProtocolValue>& proxyProtocolValues_, std::string& requestorId_, std::string& deviceId_, std::string& deviceName_, std::string& routingTag_, boost::optional<int>& rcode_, uint32_t& ttlCap_, bool& variable_, bool tcp_, bool& logQuery_, bool& logResponse_, bool& followCNAMERecords_, boost::optional<uint16_t>& extendedErrorCode_, std::string& extendedErrorExtra_): qname(qname_), local(local_), remote(remote_), ednssubnet(ednssubnet_), policyTags(policyTags_), records(records_), ednsOptions(ednsOptions_), proxyProtocolValues(proxyProtocolValues_), requestorId(requestorId_), deviceId(deviceId_), deviceName(deviceName_), routingTag(routingTag_), extendedErrorExtra(extendedErrorExtra_), rcode(rcode_), extendedErrorCode(extendedErrorCode_), ttlCap(ttlCap_), variable(variable_), logQuery(logQuery_), logResponse(logResponse_), followCNAMERecords(followCNAMERecords_), qtype(qtype_), tcp(tcp_)
   {
   }
 
@@ -601,7 +625,9 @@ public:
   std::string& deviceId;
   std::string& deviceName;
   std::string& routingTag;
+  std::string& extendedErrorExtra;
   boost::optional<int>& rcode;
+  boost::optional<uint16_t>& extendedErrorCode;
   uint32_t& ttlCap;
   bool& variable;
   bool& logQuery;
@@ -613,10 +639,10 @@ public:
   bool tcp;
 };
 
-unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const
+unsigned int RecursorLua4::gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords, boost::optional<uint16_t>& extendedErrorCode, std::string& extendedErrorExtra) const
 {
   if (d_gettag_ffi) {
-    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords);
+    pdns_ffi_param_t param(qname, qtype, local, remote, ednssubnet, *policyTags, records, ednsOptions, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, tcp, logQuery, logResponse, followCNAMERecords, extendedErrorCode, extendedErrorExtra);
 
     auto ret = d_gettag_ffi(&param);
     if (ret) {
@@ -930,6 +956,16 @@ void pdns_ffi_param_set_follow_cname_records(pdns_ffi_param_t* ref, bool follow)
   ref->followCNAMERecords = follow;
 }
 
+void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code)
+{
+  ref->extendedErrorCode = code;
+}
+
+void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra)
+{
+  ref->extendedErrorExtra = std::string(extra, len);
+}
+
 bool pdns_ffi_param_add_record(pdns_ffi_param_t *ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentSize, pdns_record_place_t place)
 {
   try {
index 3b911eed181b5f49855818a0b7b721648b8ab9d9..1bdd02b10f76a68f470f08290662765bee9021f4 100644 (file)
@@ -78,6 +78,8 @@ public:
     std::unordered_set<std::string>* policyTags{nullptr};
     const std::vector<ProxyProtocolValue>* proxyProtocolValues{nullptr};
     std::unordered_map<std::string, bool>* discardedPolicies{nullptr};
+    std::string* extendedErrorExtra{nullptr};
+    boost::optional<uint16_t>* extendedErrorCode{nullptr};
     std::string requestorId;
     std::string deviceId;
     std::string deviceName;
@@ -130,7 +132,7 @@ public:
   };
 
   unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
-  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
+  unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords, boost::optional<uint16_t>& extendedErrorCode, std::string& extendedErrorExtra) const;
 
   void maintenance() const;
   bool prerpz(DNSQuestion& dq, int& ret) const;
index 8aaa1d3a96c021daa172ce886edceedf65db596d..e9589c2bd3c61dea8be636ca63ccc6558366e7c5 100644 (file)
@@ -344,6 +344,8 @@ struct DNSComboWriter {
   LuaContext::LuaObject d_data;
   EDNSSubnetOpts d_ednssubnet;
   shared_ptr<TCPConnection> d_tcpConnection;
+  boost::optional<uint16_t> d_extendedErrorCode{boost::none};
+  string d_extendedErrorExtra;
   boost::optional<int> d_rcode{boost::none};
   int d_socket{-1};
   unsigned int d_tag{0};
@@ -1526,6 +1528,8 @@ static void startDoResolve(void *p)
     dq.deviceName = dc->d_deviceName;
 #endif
     dq.proxyProtocolValues = &dc->d_proxyProtocolValues;
+    dq.extendedErrorCode = &dc->d_extendedErrorCode;
+    dq.extendedErrorExtra = &dc->d_extendedErrorExtra;
 
     if(ednsExtRCode != 0) {
       goto sendit;
@@ -1858,62 +1862,73 @@ static void startDoResolve(void *p)
 
     if (haveEDNS) {
       auto state = sr.getValidationState();
-      if (s_addExtendedDNSErrors && vStateIsBogus(state) && pw.size() < maxanswersize && (maxanswersize - pw.size()) > (2 + 2 + 2)) {
+      if (s_addExtendedDNSErrors && (dc->d_extendedErrorCode || vStateIsBogus(state))) {
         EDNSExtendedError::code code;
+        std::string extra;
 
-        switch (state) {
-        case vState::BogusNoValidDNSKEY:
-          code = EDNSExtendedError::code::DNSKEYMissing;
-          break;
-        case vState::BogusInvalidDenial:
-          code = EDNSExtendedError::code::NSECMissing;
-          break;
-        case vState::BogusUnableToGetDSs:
-          code = EDNSExtendedError::code::DNSSECBogus;
-          break;
-        case vState::BogusUnableToGetDNSKEYs:
-          code = EDNSExtendedError::code::DNSKEYMissing;
-          break;
-        case vState::BogusSelfSignedDS:
-          code = EDNSExtendedError::code::DNSSECBogus;
-          break;
-        case vState::BogusNoRRSIG:
-          code = EDNSExtendedError::code::RRSIGsMissing;
-          break;
-        case vState::BogusNoValidRRSIG:
-          code = EDNSExtendedError::code::DNSSECBogus;
-          break;
-        case vState::BogusMissingNegativeIndication:
-          code = EDNSExtendedError::code::NSECMissing;
-          break;
-        case vState::BogusSignatureNotYetValid:
-          code = EDNSExtendedError::code::SignatureNotYetValid;
-          break;
-        case vState::BogusSignatureExpired:
-          code = EDNSExtendedError::code::SignatureExpired;
-          break;
-        case vState::BogusUnsupportedDNSKEYAlgo:
-          code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm;
-          break;
-        case vState::BogusUnsupportedDSDigestType:
-          code = EDNSExtendedError::code::UnsupportedDSDigestType;
-          break;
-        case vState::BogusNoZoneKeyBitSet:
-          code = EDNSExtendedError::code::NoZoneKeyBitSet;
-          break;
-        case vState::BogusRevokedDNSKEY:
-          code = EDNSExtendedError::code::DNSSECBogus;
-          break;
-        case vState::BogusInvalidDNSKEYProtocol:
-          code = EDNSExtendedError::code::DNSSECBogus;
-          break;
-        default:
-          throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state));
+        if (dc->d_extendedErrorCode) {
+          code = static_cast<EDNSExtendedError::code>(*dc->d_extendedErrorCode);
+          extra = std::move(dc->d_extendedErrorExtra);
+        }
+        else {
+          switch (state) {
+          case vState::BogusNoValidDNSKEY:
+            code = EDNSExtendedError::code::DNSKEYMissing;
+            break;
+          case vState::BogusInvalidDenial:
+            code = EDNSExtendedError::code::NSECMissing;
+            break;
+          case vState::BogusUnableToGetDSs:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusUnableToGetDNSKEYs:
+            code = EDNSExtendedError::code::DNSKEYMissing;
+            break;
+          case vState::BogusSelfSignedDS:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusNoRRSIG:
+            code = EDNSExtendedError::code::RRSIGsMissing;
+            break;
+          case vState::BogusNoValidRRSIG:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusMissingNegativeIndication:
+            code = EDNSExtendedError::code::NSECMissing;
+            break;
+          case vState::BogusSignatureNotYetValid:
+            code = EDNSExtendedError::code::SignatureNotYetValid;
+            break;
+          case vState::BogusSignatureExpired:
+            code = EDNSExtendedError::code::SignatureExpired;
+            break;
+          case vState::BogusUnsupportedDNSKEYAlgo:
+            code = EDNSExtendedError::code::UnsupportedDNSKEYAlgorithm;
+            break;
+          case vState::BogusUnsupportedDSDigestType:
+            code = EDNSExtendedError::code::UnsupportedDSDigestType;
+            break;
+          case vState::BogusNoZoneKeyBitSet:
+            code = EDNSExtendedError::code::NoZoneKeyBitSet;
+            break;
+          case vState::BogusRevokedDNSKEY:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          case vState::BogusInvalidDNSKEYProtocol:
+            code = EDNSExtendedError::code::DNSSECBogus;
+            break;
+          default:
+            throw std::runtime_error("Bogus validation state not handled: " + vStateToString(state));
+          }
         }
 
         EDNSExtendedError eee;
         eee.infoCode = static_cast<uint16_t>(code);
-        returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee)));
+        eee.extraText = std::move(extra);
+
+        if (pw.size() < maxanswersize && (maxanswersize - pw.size()) >= (2 + 2 + 2 + eee.extraText.size())) {
+          returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::EXTENDEDERROR, makeEDNSExtendedErrorOptString(eee)));
+        }
       }
 
       /* we try to add the EDNS OPT RR even for truncated answers,
@@ -2474,7 +2489,7 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
           if(t_pdl) {
             try {
               if (t_pdl->d_gettag_ffi) {
-                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords);
+                dc->d_tag = t_pdl->gettag_ffi(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_records, dc->d_data, ednsOptions, true, dc->d_proxyProtocolValues, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_rcode, dc->d_ttlCap, dc->d_variable, logQuery, dc->d_logResponse, dc->d_followCNAMERecords, dc->d_extendedErrorCode, dc->d_extendedErrorExtra);
               }
               else if (t_pdl->d_gettag) {
                 dc->d_tag = t_pdl->gettag(dc->d_source, dc->d_ednssubnet.source, dc->d_destination, qname, qtype, &dc->d_policyTags, dc->d_data, ednsOptions, true, requestorId, deviceId, deviceName, dc->d_routingTag, dc->d_proxyProtocolValues);
@@ -2700,7 +2715,9 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
   bool ecsFound = false;
   bool ecsParsed = false;
   std::vector<DNSRecord> records;
+  std::string extendedErrorExtra;
   boost::optional<int> rcode = boost::none;
+  boost::optional<uint16_t> extendedErrorCode{boost::none};
   uint32_t ttlCap = std::numeric_limits<uint32_t>::max();
   bool variable = false;
   bool followCNAMEs = false;
@@ -2739,7 +2756,7 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
         if(t_pdl) {
           try {
             if (t_pdl->d_gettag_ffi) {
-              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs);
+              ctag = t_pdl->gettag_ffi(source, ednssubnet.source, destination, qname, qtype, &policyTags, records, data, ednsOptions, false, proxyProtocolValues, requestorId, deviceId, deviceName, routingTag, rcode, ttlCap, variable, logQuery, logResponse, followCNAMEs, extendedErrorCode, extendedErrorExtra);
             }
             else if (t_pdl->d_gettag) {
               ctag = t_pdl->gettag(source, ednssubnet.source, destination, qname, qtype, &policyTags, data, ednsOptions, false, requestorId, deviceId, deviceName, routingTag, proxyProtocolValues);
@@ -2910,6 +2927,8 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
 #endif
   dc->d_proxyProtocolValues = std::move(proxyProtocolValues);
   dc->d_routingTag = std::move(routingTag);
+  dc->d_extendedErrorCode = extendedErrorCode;
+  dc->d_extendedErrorExtra = std::move(extendedErrorExtra);
 
   MT->makeThread(startDoResolve, (void*) dc.release()); // deletes dc
   return 0;
index 433cb0b4c76837aa07f2a89558def840135c7060..20cd19481c088d81fc2b4e035d7ba01831372bd5 100644 (file)
@@ -204,7 +204,7 @@ BOOST_AUTO_TEST_CASE(test_makeEDNSExtendedErrorOptString)
 
   std::string extra;
   /* the size of an EDNS option is limited to 2^16-1, and in this case the code already adds 2 bytes */
-  extra.resize(65535);
+  extra.resize(65534);
   BOOST_CHECK_THROW(checkExtendedErrorOptionValidity(EDNSExtendedError::code::Other, extra), std::runtime_error);
 
   EDNSExtendedError parsed;
deleted file mode 100644 (file)
index d977e1030db6f83991dcd200c33bd68d0df0c2a6..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,98 +0,0 @@
-#!/usr/bin/env python2
-
-import dns
-import dns.edns
-import dns.flags
-import dns.message
-import dns.query
-
-class CookiesOption(dns.edns.Option):
-    """Implementation of draft-ietf-dnsop-cookies-09.
-    """
-
-    def __init__(self, client, server):
-        super(CookiesOption, self).__init__(10)
-
-        if len(client) != 8:
-            raise Exception('invalid client cookie length')
-
-        if server is not None and len(server) != 0 and (len(server) < 8 or len(server) > 32):
-            raise Exception('invalid server cookie length')
-
-        self.client = client
-        self.server = server
-
-    def to_wire(self, file=None):
-        """Create EDNS packet as defined in draft-ietf-dnsop-cookies-09."""
-
-        if self.server and len(self.server) > 0:
-            data = self.client + self.server
-        else:
-            data = self.client
-
-        if file:
-            file.write(data)
-        else:
-            return data
-
-    def from_wire(cls, otype, wire, current, olen):
-        """Read EDNS packet as defined in draft-ietf-dnsop-cookies-09.
-
-        Returns:
-            An instance of CookiesOption based on the EDNS packet
-        """
-
-        data = wire[current:current + olen]
-        if len(data) != 8 and (len(data) < 16 or len(data) > 40):
-            raise Exception('Invalid EDNS Cookies option')
-
-        client = data[:8]
-        if len(data) > 8:
-            server = data[8:]
-        else:
-            server = None
-
-        return cls(client, server)
-
-    from_wire = classmethod(from_wire)
-
-    # needed in 2.0.0
-    @classmethod
-    def from_wire_parser(cls, otype, parser):
-        data = parser.get_remaining()
-
-        if len(data) != 8 and (len(data) < 16 or len(data) > 40):
-            raise Exception('Invalid EDNS Cookies option')
-
-        client = data[:8]
-        if len(data) > 8:
-            server = data[8:]
-        else:
-            server = None
-
-        return cls(client, server)
-
-    def __repr__(self):
-        return '%s(%s, %s)' % (
-            self.__class__.__name__,
-            self.client,
-            self.server
-        )
-
-    def to_text(self):
-        return self.__repr__()
-
-    def __eq__(self, other):
-        if not isinstance(other, CookiesOption):
-            return False
-        if self.client != other.client:
-            return False
-        if self.server != other.server:
-            return False
-        return True
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-
-dns.edns._type_to_class[0x000A] = CookiesOption
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..11e592d1d8b5171c79b16f6d45e89d8555c27d7a
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.dnsdist/cookiesoption.py
\ No newline at end of file
diff --git a/regression-tests.recursor-dnssec/extendederrors.py b/regression-tests.recursor-dnssec/extendederrors.py
new file mode 100644 (file)
index 0000000..29b6853
--- /dev/null
@@ -0,0 +1,89 @@
+#!/usr/bin/env python
+import struct
+
+import dns
+import dns.edns
+import dns.flags
+import dns.message
+import dns.query
+
+class ExtendedErrorOption(dns.edns.Option):
+    """Implementation of rfc8914
+    """
+
+    def __init__(self, code, extra):
+        super(ExtendedErrorOption, self).__init__(15)
+
+        self.code = code
+        self.extra = extra
+
+    def to_wire(self, file=None):
+        """Create EDNS packet."""
+
+        data = struct.pack('!H', self.code)
+        data = data + self.extra
+        if file:
+            file.write(data)
+        else:
+            return data
+
+    def from_wire(cls, otype, wire, current, olen):
+        """Read EDNS packet.
+
+        Returns:
+            An instance of ExtendedErrorOption based on the EDNS packet
+        """
+
+        if olen < 2:
+            raise Exception('Invalid EDNS Extended Error option')
+
+        (code,) = struct.unpack('!H', wire[current:current+2])
+        if olen > 2:
+            extra = wire[current + 2:current + olen]
+        else:
+            extra = b''
+
+        return cls(code, extra)
+
+    from_wire = classmethod(from_wire)
+
+    # needed in 2.0.0
+    @classmethod
+    def from_wire_parser(cls, otype, parser):
+        data = parser.get_remaining()
+
+        if len(data) < 2:
+            raise Exception('Invalid EDNS Extended Error option')
+
+        (code,) = struct.unpack('!H', data[0:2])
+        if len(data) > 2:
+            extra = data[2:]
+        else:
+            extra = b''
+
+        return cls(code, extra)
+
+    def __repr__(self):
+        return '%s(%d, %s)' % (
+            self.__class__.__name__,
+            self.code,
+            self.extra
+        )
+
+    def to_text(self):
+        return self.__repr__()
+
+    def __eq__(self, other):
+        if not isinstance(other, ExtendedErrorOption):
+            return False
+        if self.code != other.code:
+            return False
+        if self.extra != other.extra:
+            return False
+        return True
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+dns.edns._type_to_class[0x000F] = ExtendedErrorOption
diff --git a/regression-tests.recursor-dnssec/test_ExtendedErrors.py b/regression-tests.recursor-dnssec/test_ExtendedErrors.py
new file mode 100644 (file)
index 0000000..d146720
--- /dev/null
@@ -0,0 +1,234 @@
+import dns
+import os
+import extendederrors
+
+from recursortests import RecursorTest
+
+class ExtendedErrorsRecursorTest(RecursorTest):
+
+    _confdir = 'ExtendedErrors'
+    _config_template_default = """
+dnssec=validate
+daemon=no
+trace=yes
+packetcache-ttl=0
+packetcache-servfail-ttl=0
+max-cache-ttl=15
+threads=1
+loglevel=9
+disable-syslog=yes
+log-common-errors=yes
+"""
+    _config_template = """
+    extended-errors=yes
+    """
+    _lua_dns_script_file = """
+    function preresolve(dq)
+      if dq.qname == newDN('fromlua.extended.') then
+        dq.extendedErrorCode = 10
+        dq.extendedErrorExtra = "Extra text from Lua!"
+        return true
+      end
+      if dq.qname == newDN('toolarge.extended.') then
+        dq:addRecord(pdns.TXT, '%s', pdns.place.ANSWER)
+        dq.extendedErrorCode = 10
+        dq.extendedErrorExtra = "Extra text from Lua!"
+        return true
+      end
+      return false
+    end
+
+    local ffi = require("ffi")
+
+    ffi.cdef[[
+      typedef struct pdns_ffi_param pdns_ffi_param_t;
+
+      const char* pdns_ffi_param_get_qname(pdns_ffi_param_t* ref) __attribute__ ((visibility ("default")));
+      void pdns_ffi_param_set_rcode(pdns_ffi_param_t* ref, int rcode) __attribute__ ((visibility ("default")));
+      void pdns_ffi_param_set_extended_error_code(pdns_ffi_param_t* ref, uint16_t code) __attribute__ ((visibility ("default")));
+      void pdns_ffi_param_set_extended_error_extra(pdns_ffi_param_t* ref, size_t len, const char* extra);
+    ]]
+
+    function gettag_ffi(obj)
+      local qname = ffi.string(ffi.C.pdns_ffi_param_get_qname(obj))
+      if qname == 'fromluaffi.extended' then
+        ffi.C.pdns_ffi_param_set_rcode(obj, 0)
+        ffi.C.pdns_ffi_param_set_extended_error_code(obj, 10)
+        local extra = 'Extra text from Lua FFI!'
+        ffi.C.pdns_ffi_param_set_extended_error_extra(obj, #extra, extra)
+      end
+      return {}
+    end
+    """ % ('A'*427)
+
+    _roothints = None
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        super(ExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def testNotIncepted(self):
+        qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(8, b''))
+
+    def testExpired(self):
+        qname = 'sigexpired.bad-dnssec.wb.sidnlabs.nl.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(7, b''))
+
+    def testBogus(self):
+        qname = 'unknownalgorithm.bad-dnssec.wb.sidnlabs.nl.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(6, b''))
+
+    def testMissingRRSIG(self):
+        qname = 'brokendnssec.net.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b''))
+
+    def testFromLua(self):
+        qname = 'fromlua.extended.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua!'))
+
+    def testFromLuaFFI(self):
+        qname = 'fromluaffi.extended.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.NOERROR)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 1)
+            self.assertEqual(res.options[0].otype, 15)
+            self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua FFI!'))
+
+    def testTooLarge(self):
+        qname = 'toolarge.extended.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True, payload=512)
+
+        # should not have the Extended Option since the packet is too large already
+        res = self.sendUDPQuery(query, timeout=5.0)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEquals(len(res.answer), 1)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 0)
+
+        res = self.sendTCPQuery(query, timeout=5.0)
+        self.assertRcodeEqual(res, dns.rcode.NOERROR)
+        self.assertEquals(len(res.answer), 1)
+        self.assertEqual(res.edns, 0)
+        self.assertEqual(len(res.options), 1)
+        self.assertEqual(res.options[0].otype, 15)
+        self.assertEqual(res.options[0], extendederrors.ExtendedErrorOption(10, b'Extra text from Lua!'))
+
+class NoExtendedErrorsRecursorTest(RecursorTest):
+
+    _confdir = 'ExtendedErrorsDisabled'
+    _config_template_default = """
+dnssec=validate
+daemon=no
+trace=yes
+packetcache-ttl=0
+packetcache-servfail-ttl=0
+max-cache-ttl=15
+threads=1
+loglevel=9
+disable-syslog=yes
+log-common-errors=yes
+"""
+    _config_template = """
+    extended-errors=no
+    """
+    _roothints = None
+
+    @classmethod
+    def setUpClass(cls):
+
+        # we don't need all the auth stuff
+        cls.setUpSockets()
+        cls.startResponders()
+
+        confdir = os.path.join('configs', cls._confdir)
+        cls.createConfigDir(confdir)
+
+        cls.generateRecursorConfig(confdir)
+        cls.startRecursor(confdir, cls._recursorPort)
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.tearDownRecursor()
+
+    @classmethod
+    def generateRecursorConfig(cls, confdir):
+        super(NoExtendedErrorsRecursorTest, cls).generateRecursorConfig(confdir)
+
+    def testNotIncepted(self):
+        qname = 'signotincepted.bad-dnssec.wb.sidnlabs.nl.'
+        query = dns.message.make_query(qname, 'A', want_dnssec=True)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            res = sender(query, timeout=5.0)
+            self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
+            self.assertEqual(res.edns, 0)
+            self.assertEqual(len(res.options), 0)