]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: Add support for setting Extended DNS Error statuses
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 7 Nov 2023 10:53:00 +0000 (11:53 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 9 Nov 2023 13:41:34 +0000 (14:41 +0100)
This PR adds support for adding EDNS Extended DNS Error statuses
from DNSDist, via the following mechanisms:
- `SetExtendedDNSErrorAction`
- `SetExtendedDNSErrorResponseAction`
- `DNSQuestion:setExtendedDNSError(infoCode [, extraText])`
- `DNSResponse:setExtendedDNSError(infoCode [, extraText])`
- `dnsdist_ffi_dnsquestion_set_extended_dns_error(...)`

13 files changed:
pdns/dnsdist-console.cc
pdns/dnsdist-idstate.hh
pdns/dnsdist-lua-actions.cc
pdns/dnsdist-lua-bindings-dnsquestion.cc
pdns/dnsdist.cc
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-edns.cc
pdns/dnsdistdist/dnsdist-edns.hh
pdns/dnsdistdist/dnsdist-lua-ffi-interface.h
pdns/dnsdistdist/dnsdist-lua-ffi.cc
pdns/dnsdistdist/docs/reference/dq.rst
pdns/dnsdistdist/docs/rules-actions.rst
regression-tests.dnsdist/test_EDE.py [new file with mode: 0644]

index a49a50914759924ba0901b89d124c0b1891bbdeb..9dfe8159fe504c2df196bb48446f65c8a350eaf0 100644 (file)
@@ -773,6 +773,8 @@ const std::vector<ConsoleKeyword> g_consoleKeywords{
   { "SetECSPrefixLengthAction", true, "v4, v6", "Set the ECS prefix length. Subsequent rules are processed after this action" },
   { "SetMacAddrAction", true, "option", "Add the source MAC address to the query as EDNS0 option option. This action is currently only supported on Linux. Subsequent rules are processed after this action" },
   { "SetEDNSOptionAction", true, "option, data", "Add arbitrary EDNS option and data to the query. Subsequent rules are processed after this action" },
+  { "SetExtendedDNSErrorAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to the response corresponding to the current query. Subsequent rules are processed after this action" },
+  { "SetExtendedDNSErrorResponseAction", true, "infoCode [, extraText]", "Set an Extended DNS Error status that will be added to this response. Subsequent rules are processed after this action" },
   { "SetNoRecurseAction", true, "", "strip RD bit from the question, let it go through" },
   { "setOutgoingDoHWorkerThreads", true, "n", "Number of outgoing DoH worker threads" },
   { "SetProxyProtocolValuesAction", true, "values", "Set the Proxy-Protocol values for this queries to 'values'" },
index 158bae03b24ead268fbd77f141e4b2f0a8c9444a..e1fb8d38e1534b63c13d68e00078a36dd40c6950 100644 (file)
@@ -27,6 +27,7 @@
 #include "dnscrypt.hh"
 #include "dnsname.hh"
 #include "dnsdist-protocols.hh"
+#include "ednsextendederror.hh"
 #include "gettime.hh"
 #include "iputils.hh"
 #include "noinitvector.hh"
@@ -131,6 +132,7 @@ struct InternalQueryState
   std::unique_ptr<QTag> qTag{nullptr}; // 8
   std::unique_ptr<PacketBuffer> d_packet{nullptr}; // Initial packet, so we can restart the query from the response path if needed // 8
   std::unique_ptr<ProtoBufData> d_protoBufData{nullptr};
+  std::unique_ptr<EDNSExtendedError> d_extendedError{nullptr};
   boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
   ClientState* cs{nullptr}; // 8
   std::unique_ptr<DOHUnitInterface> du; // 8
index cd9daf9bc851ae6b7478ce795f4b7cce08a42f44..949e39880116d82fd4282b02e0f2e8c9560519f0 100644 (file)
@@ -2238,6 +2238,58 @@ private:
   double d_ratio{1.0};
 };
 
+class SetExtendedDNSErrorAction : public DNSAction
+{
+public:
+  // this action does not stop the processing
+  SetExtendedDNSErrorAction(uint16_t infoCode, const std::string& extraText)
+  {
+    d_ede.infoCode = infoCode;
+    d_ede.extraText = extraText;
+  }
+
+  DNSAction::Action operator()(DNSQuestion* dq, std::string* ruleresult) const override
+  {
+    dq->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+    return DNSAction::Action::None;
+  }
+
+  std::string toString() const override
+  {
+    return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+  }
+
+private:
+  EDNSExtendedError d_ede;
+};
+
+class SetExtendedDNSErrorResponseAction : public DNSResponseAction
+{
+public:
+  // this action does not stop the processing
+  SetExtendedDNSErrorResponseAction(uint16_t infoCode, const std::string& extraText)
+  {
+    d_ede.infoCode = infoCode;
+    d_ede.extraText = extraText;
+  }
+
+  DNSResponseAction::Action operator()(DNSResponse* dr, std::string* ruleresult) const override
+  {
+    dr->ids.d_extendedError = std::make_unique<EDNSExtendedError>(d_ede);
+
+    return DNSResponseAction::Action::None;
+  }
+
+  std::string toString() const override
+  {
+    return "set EDNS Extended DNS Error to " + std::to_string(d_ede.infoCode) + (d_ede.extraText.empty() ? std::string() : std::string(": \"") + d_ede.extraText + std::string("\""));
+  }
+
+private:
+  EDNSExtendedError d_ede;
+};
+
 template<typename T, typename ActionT>
 static void addAction(GlobalStateHolder<vector<T> > *someRuleActions, const luadnsrule_t& var, const std::shared_ptr<ActionT>& action, boost::optional<luaruleparams_t>& params) {
   setLuaSideEffect();
@@ -2773,4 +2825,12 @@ void setupLuaActions(LuaContext& luaCtx)
   luaCtx.writeFunction("SetAdditionalProxyProtocolValueAction", [](uint8_t type, const std::string& value) {
     return std::shared_ptr<DNSAction>(new SetAdditionalProxyProtocolValueAction(type, value));
   });
+
+  luaCtx.writeFunction("SetExtendedDNSErrorAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+    return std::shared_ptr<DNSAction>(new SetExtendedDNSErrorAction(infoCode, extraText ? *extraText : ""));
+  });
+
+  luaCtx.writeFunction("SetExtendedDNSErrorResponseAction", [](uint16_t infoCode, boost::optional<std::string> extraText) {
+    return std::shared_ptr<DNSResponseAction>(new SetExtendedDNSErrorResponseAction(infoCode, extraText ? *extraText : ""));
+  });
 }
index 314e6b2cf44d12a738cc43a4a782d24da652cd05..0bec91aac8c05f7ebfcbdd91e30028121486e2e5 100644 (file)
@@ -258,6 +258,15 @@ void setupLuaBindingsDNSQuestion(LuaContext& luaCtx)
     setEDNSOption(dq, code, data);
   });
 
+  luaCtx.registerFunction<void(DNSQuestion::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSQuestion& dq, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+    EDNSExtendedError ede;
+    ede.infoCode = infoCode;
+    if (extraText) {
+      ede.extraText = *extraText;
+    }
+    dq.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+  });
+
   luaCtx.registerFunction<bool(DNSQuestion::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSQuestion& dq, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
     dq.asynchronous = true;
     return dnsdist::suspendQuery(dq, asyncID, queryID, timeoutMs);
@@ -506,6 +515,15 @@ private:
       return setNegativeAndAdditionalSOA(dq, nxd, DNSName(zone), ttl, DNSName(mname), DNSName(rname), serial, refresh, retry, expire, minimum, false);
     });
 
+  luaCtx.registerFunction<void(DNSResponse::*)(uint16_t infoCode, const boost::optional<std::string>& extraText)>("setExtendedDNSError", [](DNSResponse& dr, uint16_t infoCode, const boost::optional<std::string>& extraText) {
+    EDNSExtendedError ede;
+    ede.infoCode = infoCode;
+    if (extraText) {
+      ede.extraText = *extraText;
+    }
+    dr.ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+  });
+
   luaCtx.registerFunction<bool(DNSResponse::*)(uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs)>("suspend", [](DNSResponse& dr, uint16_t asyncID, uint16_t queryID, uint32_t timeoutMs) {
     dr.asynchronous = true;
     return dnsdist::suspendResponse(dr, asyncID, queryID, timeoutMs);
index dc490d2e220828c0d76ee443f20c2d1c7fcfa441..00a256942604bde744f7ffacbae28728c92548cb 100644 (file)
@@ -56,6 +56,7 @@
 #include "dnsdist-dnsparser.hh"
 #include "dnsdist-dynblocks.hh"
 #include "dnsdist-ecs.hh"
+#include "dnsdist-edns.hh"
 #include "dnsdist-healthchecks.hh"
 #include "dnsdist-lua.hh"
 #include "dnsdist-nghttp2.hh"
@@ -580,6 +581,10 @@ bool processResponseAfterRules(PacketBuffer& response, const std::vector<DNSDist
     ac(&dr, &result);
   }
 
+  if (dr.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dr.getMutableData(), dr.getMaximumSize(), dr.ids.d_extendedError->infoCode, dr.ids.d_extendedError->extraText);
+  }
+
 #ifdef HAVE_DNSCRYPT
   if (!muted) {
     if (!encryptResponse(response, dr.getMaximumSize(), dr.overTCP(), dr.ids.dnsCryptQuery)) {
@@ -1326,6 +1331,10 @@ static bool prepareOutgoingResponse(LocalHolders& holders, const ClientState& cs
     ac(&dr, &result);
   }
 
+  if (dr.ids.d_extendedError) {
+    dnsdist::edns::addExtendedDNSError(dr.getMutableData(), dr.getMaximumSize(), dr.ids.d_extendedError->infoCode, dr.ids.d_extendedError->extraText);
+  }
+
   if (cacheHit) {
     ++dnsdist::metrics::g_stats.cacheHits;
   }
index 1f64134566f88ef771d6ee306940b265365f9f09..d463c5bfb88df512fcdfc68865f77d9b9aa326fe 100644 (file)
@@ -214,6 +214,7 @@ dnsdist_SOURCES = \
        dolog.hh \
        doq.hh \
        ednscookies.cc ednscookies.hh \
+       ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/json11/json11.cpp \
index 1131bf29bc66150c0effe25d86d2a8db1b0b3260..0aa85397139585d72f8f7f430aa3d3362ab64035 100644 (file)
@@ -22,6 +22,7 @@
 #include "dnsdist-ecs.hh"
 #include "dnsdist-edns.hh"
 #include "ednsoptions.hh"
+#include "ednsextendederror.hh"
 
 namespace dnsdist::edns
 {
@@ -55,4 +56,39 @@ std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSErr
   }
   return {infoCode, std::move(extraText)};
 }
+
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus)
+{
+  uint16_t optStart = 0;
+  size_t optLen = 0;
+  bool last = false;
+
+  int res = locateEDNSOptRR(packet, &optStart, &optLen, &last);
+
+  if (res != 0) {
+    /* no EDNS OPT record in the response, something is not right */
+    return false;
+  }
+
+  EDNSExtendedError ede{.infoCode = code, .extraText = extraStatus};
+  auto edeOptionPayload = makeEDNSExtendedErrorOptString(ede);
+  std::string edeOption;
+  generateEDNSOption(EDNSOptionCode::EXTENDEDERROR, edeOptionPayload, edeOption);
+
+  /* we might have one record after the OPT one, we need to rewrite
+     the whole packet because of compression */
+  PacketBuffer newContent;
+  bool ednsAdded = false;
+  bool edeAdded = false;
+  if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::EXTENDEDERROR, edeAdded, true, edeOption)) {
+    return false;
+  }
+
+  if (newContent.size() > maximumPacketSize) {
+    return false;
+  }
+
+  packet = std::move(newContent);
+  return true;
+}
 }
index 75c92c143ac1367710c2ca39514cace500e7f08b..8e60e5b049dc1162465fbe1e20e355628ec74045 100644 (file)
@@ -30,4 +30,5 @@
 namespace dnsdist::edns
 {
 std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet);
+bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, uint16_t code, const std::string& extraStatus);
 }
index e7cc8fe873335b3f43dd2079c4bd00383b5cad56..38689663c1eeef0b2e125c013168aa684d9d7bef 100644 (file)
@@ -117,6 +117,8 @@ void dnsdist_ffi_dnsquestion_set_device_name(dnsdist_ffi_dnsquestion_t* dq, cons
 
 void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, uint16_t statusCode, const char* body, size_t bodyLen, const char* contentType) __attribute__ ((visibility ("default")));
 
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dq, uint16_t infoCode, const char* extraText, size_t extraTextSize) __attribute__ ((visibility ("default")));
+
 size_t dnsdist_ffi_dnsquestion_get_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char** out) __attribute__ ((visibility ("default")));
 
 bool dnsdist_ffi_dnsquestion_set_trailing_data(dnsdist_ffi_dnsquestion_t* dq, const char* data, size_t dataLen) __attribute__ ((visibility ("default")));
index 70f0ff2ab015339a35734956f901be5fc6556757..d2d960a947267086d3967b4def5befd8c876fc2c 100644 (file)
@@ -465,6 +465,16 @@ void dnsdist_ffi_dnsquestion_set_http_response(dnsdist_ffi_dnsquestion_t* dq, ui
 #endif
 }
 
+void dnsdist_ffi_dnsquestion_set_extended_dns_error(dnsdist_ffi_dnsquestion_t* dq, uint16_t infoCode, const char* extraText, size_t extraTextSize)
+{
+  EDNSExtendedError ede;
+  ede.infoCode = infoCode;
+  if (extraText != nullptr && extraTextSize > 0) {
+    ede.extraText = std::string(extraText, extraTextSize);
+  }
+  dq->dq->ids.d_extendedError = std::make_unique<EDNSExtendedError>(ede);
+}
+
 void dnsdist_ffi_dnsquestion_set_rcode(dnsdist_ffi_dnsquestion_t* dq, int rcode)
 {
   dnsdist::PacketMangling::editDNSHeaderFromPacket(dq->dq->getMutableData(), [rcode](dnsheader& header) {
index 33e209e5539daf53bf69e14fd337912867cecb5a..4aa675fb1e3a1396e144fc521984579800487dd8 100644 (file)
@@ -279,6 +279,15 @@ This state can be modified from the various hooks.
     :param int code: The EDNS option code
     :param string data: The EDNS option raw data
 
+  .. method:: DNSQuestion:setExtendedDNSError(infoCode [, extraText])
+
+    .. versionadded:: 1.9.0
+
+      Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+
+    :param int infoCode: The EDNS Extended DNS Error code
+    :param string extraText: The optional EDNS Extended DNS Error extra text
+
   .. method:: DNSQuestion:setHTTPResponse(status, body, contentType="")
 
     .. versionadded:: 1.4.0
index 2fb0081eb67cae8d99e8b06f799c67e5aaa65a72..090b5a7701782e51e75a237ffb53db15de602a5a 100644 (file)
@@ -938,10 +938,6 @@ Some actions allow further processing of rules, this is noted in their descripti
 - :func:`NoneAction`
 - :func:`RemoteLogAction`
 - :func:`RemoteLogResponseAction`
-- :func:`SetMaxReturnedTTLResponseAction`
-- :func:`SetMaxReturnedTTLAction`
-- :func:`SetMinTTLResponseAction`
-- :func:`SetMaxTTLResponseAction`
 - :func:`SNMPTrapAction`
 - :func:`SNMPTrapResponseAction`
 - :func:`TeeAction`
@@ -1515,6 +1511,26 @@ The following actions exist.
   :param int option: The EDNS option number
   :param string data: The EDNS0 option raw content
 
+.. function:: SetExtendedDNSErrorAction(infoCode [, extraText])
+
+  .. versionadded:: 1.9.0
+
+  Set an Extended DNS Error status that will be added to the response corresponding to the current query.
+  Subsequent rules are processed after this action.
+
+  :param int infoCode: The EDNS Extended DNS Error code
+  :param string extraText: The optional EDNS Extended DNS Error extra text
+
+.. function:: SetExtendedDNSErrorResponseAction(infoCode [, extraText])
+
+  .. versionadded:: 1.9.0
+
+  Set an Extended DNS Error status that will be added to this response.
+  Subsequent rules are processed after this action.
+
+  :param int infoCode: The EDNS Extended DNS Error code
+  :param string extraText: The optional EDNS Extended DNS Error extra text
+
 .. function:: SetMacAddrAction(option)
 
   .. versionadded:: 1.6.0
diff --git a/regression-tests.dnsdist/test_EDE.py b/regression-tests.dnsdist/test_EDE.py
new file mode 100644 (file)
index 0000000..a681424
--- /dev/null
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+import extendederrors
+import dns
+from dnsdisttests import DNSDistTest, pickAvailablePort
+
+class TestBasics(DNSDistTest):
+
+    _config_template = """
+    newServer{address="127.0.0.1:%s"}
+    pc = newPacketCache(100, {maxTTL=86400, minTTL=1})
+    getPool(""):setCache(pc)
+
+    local ffi = require("ffi")
+    function ffiAction(dq)
+      local extraText = 'Synthesized from Lua'
+      ffi.C.dnsdist_ffi_dnsquestion_set_extended_dns_error(dq, 29, extraText, #extraText)
+      local str = "192.0.2.2"
+      local buf = ffi.new("char[?]", #str + 1)
+      ffi.copy(buf, str)
+      ffi.C.dnsdist_ffi_dnsquestion_set_result(dq, buf, #str)
+      return DNSAction.Spoof
+    end
+
+    addAction("self-answered.ede.tests.powerdns.com.", SpoofAction("192.0.2.1"))
+    addAction("self-answered-ffi.ede.tests.powerdns.com.", LuaFFIAction(ffiAction))
+    addSelfAnsweredResponseAction("self-answered.ede.tests.powerdns.com.", SetExtendedDNSErrorResponseAction(42, "my self-answered extended error status"))
+    addAction(AllRule(), SetExtendedDNSErrorAction(16, "my extended error status"))
+
+    """
+
+    def testExtendedErrorNoEDNS(self):
+        """
+        EDE: No EDNS
+        """
+        name = 'no-edns.ede.tests.powerdns.com.'
+        # no EDNS
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=False)
+        response = dns.message.make_response(query)
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        response.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, response)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkResponseNoEDNS(response, receivedResponse)
+
+    def testExtendedErrorBackendResponse(self):
+        """
+        EDE: Backend response
+        """
+        name = 'backend-response.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(16, b'my extended error status')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+
+        backendResponse = dns.message.make_response(query)
+        backendResponse.use_edns(edns=True, payload=4096, options=[])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+
+        backendResponse.answer.append(rrset)
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=4096, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '127.0.0.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (receivedQuery, receivedResponse) = sender(query, backendResponse)
+            receivedQuery.id = query.id
+            self.assertEqual(query, receivedQuery)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+        # testing the cache
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+    def testExtendedErrorSelfAnswered(self):
+        """
+        EDE: Self-answered
+        """
+        name = 'self-answered.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(42, b'my self-answered extended error status')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        # dnsdist sets RA = RD for self-generated responses
+        query.flags &= ~dns.flags.RD
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.1')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)
+
+    def testExtendedErrorLuaFFI(self):
+        """
+        EDE: Self-answered via Lua FFI
+        """
+        name = 'self-answered-ffi.ede.tests.powerdns.com.'
+        ede = extendederrors.ExtendedErrorOption(29, b'Synthesized from Lua')
+        query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+        # dnsdist sets RA = RD for self-generated responses
+        query.flags &= ~dns.flags.RD
+
+        expectedResponse = dns.message.make_response(query)
+        expectedResponse.use_edns(edns=True, payload=1232, options=[ede])
+        rrset = dns.rrset.from_text(name,
+                                    60,
+                                    dns.rdataclass.IN,
+                                    dns.rdatatype.A,
+                                    '192.0.2.2')
+        expectedResponse.answer.append(rrset)
+
+        for method in ("sendUDPQuery", "sendTCPQuery"):
+            sender = getattr(self, method)
+            (_, receivedResponse) = sender(query, response=None, useQueue=False)
+            self.checkMessageEDNS(expectedResponse, receivedResponse)