]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add EDNS padding support 17050/head
authorEnsar Sarajčić <dev@ensarsarajcic.com>
Fri, 27 Mar 2026 17:08:22 +0000 (18:08 +0100)
committerEnsar Sarajčić <dev@ensarsarajcic.com>
Mon, 30 Mar 2026 15:00:48 +0000 (17:00 +0200)
Adds support for EDNS padding from [RFC 7830],
implemented per [RFC 8467], specifically [Block-Length Padding Strategy],
which is used in recursor too.

Support is added for DoT, DoH, DoH3 and DoQ frontends.

[RFC 7830]: https://datatracker.ietf.org/doc/html/rfc7830
[RFC 8467]: https://datatracker.ietf.org/doc/html/rfc8467
[Block-Length Padding Strategy]: https://datatracker.ietf.org/doc/html/rfc8467#section-4.1

Closes: #10018
Signed-off-by: Ensar Sarajčić <dev@ensarsarajcic.com>
19 files changed:
pdns/dnsdistdist/Makefile.am
pdns/dnsdistdist/dnsdist-configuration-yaml.cc
pdns/dnsdistdist/dnsdist-edns.cc
pdns/dnsdistdist/dnsdist-edns.hh
pdns/dnsdistdist/dnsdist-lua.cc
pdns/dnsdistdist/dnsdist-settings-definitions.yml
pdns/dnsdistdist/dnsdist.cc
pdns/dnsdistdist/dnsdist.hh
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/ednspadding.cc [new symlink]
pdns/dnsdistdist/ednspadding.hh [new symlink]
pdns/dnsdistdist/meson.build
pdns/dnsdistdist/test-dnsdist-lua-ffi.cc
pdns/dnsdistdist/test-dnsdistnghttp2-in_cc.cc
pdns/dnsdistdist/test-dnsdistnghttp2_cc.cc
pdns/dnsdistdist/test-dnsdisttcp_cc.cc
regression-tests.common/paddingoption.py [new file with mode: 0644]
regression-tests.dnsdist/test_DOH.py
regression-tests.recursor-dnssec/paddingoption.py [changed from file to symlink]

index 1502e91138328c7b81f475a9f50b0c2c369d862a..856916a9ae78eb7d5435d43bd3ae9084cf63865d 100644 (file)
@@ -273,6 +273,7 @@ dnsdist_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednspadding.cc ednspadding.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/json11/json11.cpp \
        ext/json11/json11.hpp \
@@ -382,6 +383,7 @@ testrunner_SOURCES = \
        ednscookies.cc ednscookies.hh \
        ednsextendederror.cc ednsextendederror.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednspadding.cc ednspadding.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/json11/json11.cpp \
        ext/json11/json11.hpp \
@@ -647,6 +649,7 @@ fuzz_target_dnsdistcache_SOURCES = \
        dnswriter.cc dnswriter.hh \
        dolog.cc dolog.hh \
        ednsoptions.cc ednsoptions.hh \
+       ednspadding.cc ednspadding.hh \
        ednssubnet.cc ednssubnet.hh \
        ext/json11/json11.cpp \
        ext/json11/json11.hpp \
index 48eff1d21670d84dae0a931676f1c5c838d5d65e..0ba3f9cad1b7898460b6e0aede78a40380083845 100644 (file)
@@ -806,7 +806,7 @@ static void loadBinds(const Context& context, const ::rust::Vec<dnsdist::rust::s
         std::shared_ptr<DNSCryptContext> dnsCryptContext;
 #endif /* defined(HAVE_DNSCRYPT) */
 
-        auto state = std::make_shared<ClientState>(listeningAddress, protocol != "doq" && protocol != "doh3", bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, bind.enable_proxy_protocol);
+        auto state = std::make_shared<ClientState>(listeningAddress, protocol != "doq" && protocol != "doh3", bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, bind.enable_proxy_protocol, bind.pad_responses);
 
         if (bind.tcp.listen_queue_size > 0) {
           state->tcpListenQueueSize = bind.tcp.listen_queue_size;
@@ -842,7 +842,7 @@ static void loadBinds(const Context& context, const ::rust::Vec<dnsdist::rust::s
         config.d_frontends.emplace_back(std::move(state));
         if (protocol == "do53" || protocol == "dnscrypt") {
           /* also create the UDP listener */
-          state = std::make_shared<ClientState>(ComboAddress(std::string(bind.listen_address), defaultPort), false, bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, bind.enable_proxy_protocol);
+          state = std::make_shared<ClientState>(ComboAddress(std::string(bind.listen_address), defaultPort), false, bind.reuseport, bind.tcp.fast_open_queue_size, std::string(bind.interface), cpus, bind.enable_proxy_protocol, bind.pad_responses);
 #if defined(HAVE_DNSCRYPT)
           state->dnscryptCtx = std::move(dnsCryptContext);
 #endif /* defined(HAVE_DNSCRYPT) */
index 3074658510bd3a4f9319790ff0dca4f23dd974a6..9eaa6114cea4eb1563fb52da24be623e3547f5c7 100644 (file)
@@ -22,6 +22,7 @@
 #include "dnsdist-ecs.hh"
 #include "dnsdist-edns.hh"
 #include "ednsoptions.hh"
+#include "ednspadding.hh"
 #include "ednsextendederror.hh"
 
 namespace dnsdist::edns
@@ -75,12 +76,62 @@ bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, const S
   std::string edeOption;
   generateEDNSOption(EDNSOptionCode::EXTENDEDERROR, edeOptionPayload, edeOption);
 
+  PacketBuffer newContent;
+  bool ednsAdded = false;
+  bool edeAdded = false;
+  if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::EXTENDEDERROR, edeAdded, setErrorOp.clearExisting, !setErrorOp.clearExisting, edeOption)) {
+    return false;
+  }
+
+  if (newContent.size() > maximumPacketSize) {
+    return false;
+  }
+
+  packet = std::move(newContent);
+  return true;
+}
+
+bool addEDNSPadding(PacketBuffer& packet, size_t maximumPacketSize)
+{
+  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;
+  }
+
+  if (isEDNSOptionInOpt(packet, optStart, optLen, EDNSOptionCode::PADDING)) {
+    /* padding is already present, exit */
+    return false;
+  }
+
+  if (packet.size() < maximumPacketSize - 4) {
+    return true;
+  }
+
+  size_t remaining = maximumPacketSize - (packet.size() + 4);
+
+  const size_t blockSize = 468;
+  size_t modulo = (packet.size() + 4) % blockSize;
+  size_t padSize = 0;
+  if (modulo > 0) {
+    padSize = std::min(blockSize - modulo, remaining);
+  }
+
+  std::string paddingPayload = makeEDNSPaddingOptString(padSize);
+  std::string padding;
+  generateEDNSOption(EDNSOptionCode::PADDING, paddingPayload, padding);
+
   /* 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, setErrorOp.clearExisting, !setErrorOp.clearExisting, edeOption)) {
+  if (!slowRewriteEDNSOptionInQueryWithRecords(packet, newContent, ednsAdded, EDNSOptionCode::PADDING, edeAdded, true, false, padding)) {
     return false;
   }
 
index f1e89221afaadc98dfcb2b7aea5ed255bb80d7ab..755a584816c3e74588ccaaefc3447fb1648e5898 100644 (file)
@@ -38,4 +38,5 @@ struct SetExtendedDNSErrorOperation
 
 std::pair<std::optional<uint16_t>, std::optional<std::string>> getExtendedDNSError(const PacketBuffer& packet);
 bool addExtendedDNSError(PacketBuffer& packet, size_t maximumPacketSize, const SetExtendedDNSErrorOperation& setErrorOp);
+bool addEDNSPadding(PacketBuffer& packet, size_t maximumPacketSize);
 }
index fd90ee55a4422868db7e5ffa8e136a156778c6fb..c4112bf6572e8dd05a5a79d864fda135fe99c91f 100644 (file)
@@ -810,8 +810,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
 
       // only works pre-startup, so no sync necessary
-      auto udpCS = std::make_shared<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
-      auto tcpCS = std::make_shared<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto udpCS = std::make_shared<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
+      auto tcpCS = std::make_shared<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
       if (tcpListenQueueSize > 0) {
         tcpCS->tcpListenQueueSize = tcpListenQueueSize;
       }
@@ -871,8 +871,8 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     try {
       ComboAddress loc(addr, 53);
       // only works pre-startup, so no sync necessary
-      auto udpCS = std::make_shared<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
-      auto tcpCS = std::make_shared<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto udpCS = std::make_shared<ClientState>(loc, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
+      auto tcpCS = std::make_shared<ClientState>(loc, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
       if (tcpListenQueueSize > 0) {
         tcpCS->tcpListenQueueSize = tcpListenQueueSize;
       }
@@ -1604,7 +1604,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       auto ctx = std::make_shared<DNSCryptContext>(providerName, certKeys);
 
       /* UDP */
-      auto clientState = std::make_shared<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto clientState = std::make_shared<ClientState>(ComboAddress(addr, 443), false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
       clientState->dnscryptCtx = ctx;
 
       dnsdist::configuration::updateImmutableConfiguration([&clientState](dnsdist::configuration::ImmutableConfiguration& config) {
@@ -1612,7 +1612,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       });
 
       /* TCP */
-      clientState = std::make_shared<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      clientState = std::make_shared<ClientState>(ComboAddress(addr, 443), true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, false);
       clientState->dnscryptCtx = std::move(ctx);
       if (tcpListenQueueSize > 0) {
         clientState->tcpListenQueueSize = tcpListenQueueSize;
@@ -2304,6 +2304,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     std::set<int> cpus;
     std::vector<std::pair<ComboAddress, int>> additionalAddresses;
     bool enableProxyProtocol = true;
+    bool padResponses = false;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
@@ -2312,6 +2313,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       getOptionalValue<std::string>(vars, "provider", frontend->d_tlsContext->d_provider);
       boost::algorithm::to_lower(frontend->d_tlsContext->d_provider);
       getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_tlsContext->d_proxyProtocolOutsideTLS);
+      getOptionalValue<bool>(vars, "padResponses", padResponses);
 
       LuaAssociativeTable<std::string> customResponseHeaders;
       if (getOptionalValue<decltype(customResponseHeaders)>(vars, "customResponseHeaders", customResponseHeaders) > 0) {
@@ -2380,7 +2382,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       }
     }
 
-    auto clientState = std::make_shared<ClientState>(frontend->d_tlsContext->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    auto clientState = std::make_shared<ClientState>(frontend->d_tlsContext->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, padResponses);
     clientState->dohFrontend = std::move(frontend);
     clientState->d_additionalAddresses = std::move(additionalAddresses);
 
@@ -2425,6 +2427,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     std::set<int> cpus;
     std::vector<std::pair<ComboAddress, int>> additionalAddresses;
     bool enableProxyProtocol = true;
+    bool padResponses = false;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
@@ -2434,6 +2437,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
       getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
       getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+      getOptionalValue<bool>(vars, "padResponses", padResponses);
       {
         std::string valueStr;
         if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
@@ -2461,7 +2465,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       checkAllParametersConsumed("addDOH3Local", vars);
     }
 
-    auto clientState = std::make_shared<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    auto clientState = std::make_shared<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, padResponses);
     clientState->doh3Frontend = std::move(frontend);
     clientState->d_additionalAddresses = std::move(additionalAddresses);
 
@@ -2499,6 +2503,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     std::set<int> cpus;
     std::vector<std::pair<ComboAddress, int>> additionalAddresses;
     bool enableProxyProtocol = true;
+    bool padResponses = false;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConnections, enableProxyProtocol);
@@ -2508,6 +2513,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       getOptionalValue<int>(vars, "internalPipeBufferSize", frontend->d_internalPipeBufferSize);
       getOptionalValue<int>(vars, "idleTimeout", frontend->d_quicheParams.d_idleTimeout);
       getOptionalValue<std::string>(vars, "keyLogFile", frontend->d_quicheParams.d_keyLogFile);
+      getOptionalValue<bool>(vars, "padResponses", padResponses);
       {
         std::string valueStr;
         if (getOptionalValue<std::string>(vars, "congestionControlAlgo", valueStr) > 0) {
@@ -2535,7 +2541,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       checkAllParametersConsumed("addDOQLocal", vars);
     }
 
-    auto clientState = std::make_shared<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+    auto clientState = std::make_shared<ClientState>(frontend->d_local, false, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, padResponses);
     clientState->doqFrontend = std::move(frontend);
     clientState->d_additionalAddresses = std::move(additionalAddresses);
 
@@ -2854,6 +2860,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     std::set<int> cpus;
     std::vector<std::pair<ComboAddress, int>> additionalAddresses;
     bool enableProxyProtocol = true;
+    bool padResponses = false;
 
     if (vars) {
       parseLocalBindVars(vars, reusePort, tcpFastOpenQueueSize, interface, cpus, tcpListenQueueSize, maxInFlightQueriesPerConn, tcpMaxConcurrentConns, enableProxyProtocol);
@@ -2861,6 +2868,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
       getOptionalValue<std::string>(vars, "provider", frontend->d_provider);
       boost::algorithm::to_lower(frontend->d_provider);
       getOptionalValue<bool>(vars, "proxyProtocolOutsideTLS", frontend->d_proxyProtocolOutsideTLS);
+      getOptionalValue<bool>(vars, "padResponses", padResponses);
 
       LuaArray<std::string> addresses;
       if (getOptionalValue<decltype(addresses)>(vars, "additionalAddresses", addresses) > 0) {
@@ -2914,7 +2922,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
                     getLogger("addTLSLocal")->info(Logr::Info, "Loading default TLS provider for DoT frontend", "frontend.address", Logging::Loggable(addr), "tls.provider", Logging::Loggable(provider)));
       }
       // only works pre-startup, so no sync necessary
-      auto clientState = std::make_shared<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol);
+      auto clientState = std::make_shared<ClientState>(frontend->d_addr, true, reusePort, tcpFastOpenQueueSize, interface, cpus, enableProxyProtocol, padResponses);
       clientState->tlsFrontend = std::move(frontend);
       clientState->d_additionalAddresses = std::move(additionalAddresses);
       if (tcpListenQueueSize > 0) {
index 24d2415e291ec0201edcd57ec79018023d7b86e9..e7ded858a8be0b5097674009f2d9780e138f716c 100644 (file)
@@ -1140,6 +1140,10 @@ bind:
       type: "bool"
       default: "true"
       description: "Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address allowed by the ACL in :ref:`yaml-settings-ProxyProtocolConfiguration`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the proxy protocol ACL"
+    - name: "pad_responses"
+      type: "bool"
+      default: "false"
+      description: "Whether to pad DNS responses as specified in RFC 7830."
     - name: "tcp"
       type: "IncomingTcpConfiguration"
       default: true
index 79c7782c7c18eab9baeddaaea63784bf8a4f6c00..cb933605734a067b387fc8e3a79727a2a653e478 100644 (file)
@@ -563,6 +563,10 @@ bool processResponseAfterRules(PacketBuffer& response, DNSResponse& dnsResponse,
     }
   }
 
+  if (dnsResponse.ids.cs->d_padResponses && !dnsResponse.ids.ednsAdded) {
+    dnsdist::edns::addEDNSPadding(dnsResponse.getMutableData(), dnsResponse.getMaximumSize());
+  }
+
 #ifdef HAVE_DNSCRYPT
   if (!muted) {
     if (!encryptResponse(response, dnsResponse.getMaximumSize(), dnsResponse.overTCP(), dnsResponse.ids.dnsCryptQuery)) {
@@ -1437,6 +1441,10 @@ static bool prepareOutgoingResponse([[maybe_unused]] const ClientState& clientSt
     }
   }
 
+  if (dnsResponse.ids.cs->d_padResponses && !dnsResponse.ids.ednsAdded) {
+    dnsdist::edns::addEDNSPadding(dnsResponse.getMutableData(), dnsResponse.getMaximumSize());
+  }
+
   if (cacheHit) {
     ++dnsdist::metrics::g_stats.cacheHits;
   }
@@ -3433,17 +3441,17 @@ static void initFrontends(const CommandLineParameters& cmdLine)
 
     for (const auto& loc : cmdLine.locals) {
       /* UDP */
-      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), false, false, 0, "", std::set<int>{}, true));
+      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), false, false, 0, "", std::set<int>{}, true, false));
       /* TCP */
-      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), true, false, 0, "", std::set<int>{}, true));
+      frontends.emplace_back(std::make_unique<ClientState>(ComboAddress(loc, 53), true, false, 0, "", std::set<int>{}, true, false));
     }
   }
 
   if (frontends.empty()) {
     /* UDP */
-    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), false, false, 0, "", std::set<int>{}, true));
+    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), false, false, 0, "", std::set<int>{}, true, false));
     /* TCP */
-    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), true, false, 0, "", std::set<int>{}, true));
+    frontends.emplace_back(std::make_unique<ClientState>(ComboAddress("127.0.0.1", 53), true, false, 0, "", std::set<int>{}, true, false));
   }
 
   dnsdist::configuration::updateImmutableConfiguration([&frontends](dnsdist::configuration::ImmutableConfiguration& config) {
index 6230c328b5dbfdf515d7d960c144a84261274445..fb94267a2c11ea5c028a4fb119847cb18b557f1a 100644 (file)
@@ -340,8 +340,8 @@ class DNSCryptContext;
 
 struct ClientState
 {
-  ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_, bool enableProxyProtocol) :
-    cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort), d_enableProxyProtocol(enableProxyProtocol)
+  ClientState(const ComboAddress& local_, bool isTCP_, bool doReusePort, int fastOpenQueue, const std::string& itfName, const std::set<int>& cpus_, bool enableProxyProtocol, bool padResponses) :
+    cpus(cpus_), interface(itfName), local(local_), fastOpenQueueSize(fastOpenQueue), tcp(isTCP_), reuseport(doReusePort), d_enableProxyProtocol(enableProxyProtocol), d_padResponses(padResponses)
   {
   }
 
@@ -392,6 +392,7 @@ struct ClientState
   bool tcp;
   bool reuseport;
   bool d_enableProxyProtocol{true}; // the global proxy protocol ACL still applies
+  bool d_padResponses{false};
   bool ready{false};
 
   int getSocket() const
index f829080933ed66824d23257475c274a321e5a5ef..4d69d8a3e9e16bbbc48fdb89636a4258a1f37c7d 100644 (file)
@@ -144,6 +144,9 @@ Listen Sockets
   .. versionchanged:: 1.9.0
      ``enableProxyProtocol``, ``ktls``, ``library``, ``proxyProtocolOutsideTLS``, ``readAhead``, ``tlsAsyncMode`` options added.
 
+  .. versionchanged:: 2.2.0
+     ``padResponses`` option added.
+
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
   More information is available in :doc:`../guides/dns-over-https`.
@@ -193,6 +196,7 @@ Listen Sockets
   * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
   * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
   * ``enableProxyProtocol=true``: bool - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+  * ``padResponses``: bool - Whether to pad DNS responses as specified in RFC 7830. Default is ``false``, meaning responses are not padded.
 
 .. function:: addDOH3Local(address, certFile(s), keyFile(s) [, options])
 
@@ -201,6 +205,9 @@ Listen Sockets
   .. versionchanged:: 2.1.0
     The default congestion algorithm used to be ``reno`` and is now ``cubic``.
 
+  .. versionchanged:: 2.2.0
+     ``padResponses`` option added.
+
   Listen on the specified address and UDP port for incoming DNS over HTTP3 connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
   More information is available in :doc:`../guides/dns-over-http3`.
 
@@ -220,6 +227,7 @@ Listen Sockets
   * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
   * ``congestionControlAlgo="cubic"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
   * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
+  * ``padResponses``: bool - Whether to pad DNS responses as specified in RFC 7830. Default is ``false``, meaning responses are not padded.
 
 .. function:: addDOQLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -228,6 +236,9 @@ Listen Sockets
   .. versionchanged:: 2.1.0
     The default congestion algorithm used to be ``reno`` and is now ``cubic``.
 
+  .. versionchanged:: 2.2.0
+     ``padResponses`` option added.
+
   Listen on the specified address and UDP port for incoming DNS over QUIC connections, presenting the specified X.509 certificate.
   See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
   More information is available at :doc:`../guides/dns-over-quic`.
@@ -248,6 +259,7 @@ Listen Sockets
   * ``maxInFlight=65535``: int - Maximum number of in-flight queries. The default is 0, which disables out-of-order processing.
   * ``congestionControlAlgo="cubic"``: str - The congestion control algorithm to be chosen between ``reno``, ``cubic`` and ``bbr``.
   * ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.
+  * ``padResponses``: bool - Whether to pad DNS responses as specified in RFC 7830. Default is ``false``, meaning responses are not padded.
 
 .. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
 
@@ -264,6 +276,8 @@ Listen Sockets
      ``additionalAddresses``, ``ignoreTLSConfigurationErrors`` and ``ktls`` options added.
   .. versionchanged:: 1.9.0
      ``enableProxyProtocol``, ``readAhead`` and ``proxyProtocolOutsideTLS`` options added.
+  .. versionchanged:: 2.2.0
+     ``padResponses`` option added.
 
   Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate. See :doc:`../advanced/tls-certificates-management` for details about the handling of TLS certificates and keys.
   More information is available at :doc:`../guides/dns-over-tls`.
@@ -305,6 +319,7 @@ Listen Sockets
   * ``readAhead``: bool - When the TLS provider is set to OpenSSL, whether we tell the library to read as many input bytes as possible, which leads to better performance by reducing the number of syscalls. Default is true.
   * ``proxyProtocolOutsideTLS``: bool - When the use of incoming proxy protocol is enabled, whether the payload is prepended after the start of the TLS session (so inside, meaning it is protected by the TLS layer providing encryption and authentication) or not (outside, meaning it is in clear-text). Default is false which means inside. Note that most third-party software like HAproxy expect the proxy protocol payload to be outside, in clear-text.
   * ``enableProxyProtocol=true``: str - Whether to expect a proxy protocol v2 header in front of incoming queries coming from an address in :func:`setProxyProtocolACL`. Default is ``true``, meaning that queries are expected to have a proxy protocol payload if they come from an address present in the :func:`setProxyProtocolACL` ACL.
+  * ``padResponses``: bool - Whether to pad DNS responses as specified in RFC 7830. Default is ``false``, meaning responses are not padded.
 
 .. function:: setLocal(address[, options])
 
diff --git a/pdns/dnsdistdist/ednspadding.cc b/pdns/dnsdistdist/ednspadding.cc
new file mode 120000 (symlink)
index 0000000..fa7a852
--- /dev/null
@@ -0,0 +1 @@
+../ednspadding.cc
\ No newline at end of file
diff --git a/pdns/dnsdistdist/ednspadding.hh b/pdns/dnsdistdist/ednspadding.hh
new file mode 120000 (symlink)
index 0000000..8701869
--- /dev/null
@@ -0,0 +1 @@
+../ednspadding.hh
\ No newline at end of file
index 535d8d0cba39ab12d0b9ce4246c87b2c99d85d44..d3731c879be729373f8459f6b4d3a20f2cdfa3c9 100644 (file)
@@ -198,6 +198,7 @@ common_sources += files(
   src_dir / 'ednscookies.cc',
   src_dir / 'ednsextendederror.cc',
   src_dir / 'ednsoptions.cc',
+  src_dir / 'ednspadding.cc',
   src_dir / 'ednssubnet.cc',
   src_dir / 'gettime.cc',
   src_dir / 'iputils.cc',
index 515d12c84047b37672cd7fdeb6f730a6b4f8b1ea..7371d19284119a0ee0484200c8277e467df98545 100644 (file)
@@ -381,7 +381,7 @@ BOOST_AUTO_TEST_CASE(test_Query)
   {
     /* frontend without and interface set */
     const std::string interface{};
-    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false);
+    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false, false);
     ids.cs = &frontend;
     const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ);
     BOOST_REQUIRE(itfPtr != nullptr);
@@ -391,7 +391,7 @@ BOOST_AUTO_TEST_CASE(test_Query)
   {
     /* frontend with interface set */
     const std::string interface{"interface-name-0"};
-    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false);
+    ClientState frontend(ids.origDest, false, false, 0, interface, {}, false, false);
     ids.cs = &frontend;
     const auto* itfPtr = dnsdist_ffi_dnsquestion_get_incoming_interface(&lightDQ);
     BOOST_REQUIRE(itfPtr != nullptr);
index feb9e408e6f518d894f4b0e158cb54eb1460187f..eeca64c5bcf2c0d335cc095b7b99370c01cacb60 100644 (file)
@@ -512,7 +512,7 @@ private:
 BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
   localCS.dohFrontend->d_urls.insert("/dns-query");
 
@@ -701,7 +701,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendTimeout, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
   localCS.dohFrontend->d_urls.insert("/dns-query");
 
@@ -774,7 +774,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendTimeout, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_ClientTimeout_BackendTimeout, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   localCS.dohFrontend = std::make_shared<DOHFrontend>(std::make_shared<MockupTLSCtx>());
   localCS.dohFrontend->d_urls.insert("/dns-query");
 
index 1ab86ea9056393c9fe4d4174c0aa39a515b10a8d..b779114859480c2fb586681e14e5684113fd8a51 100644 (file)
@@ -584,7 +584,7 @@ struct TestFixture
 BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -660,7 +660,7 @@ BOOST_FIXTURE_TEST_CASE(test_SingleQuery, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -749,7 +749,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConcurrentQueries, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -860,7 +860,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionReuse, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -942,7 +942,7 @@ BOOST_FIXTURE_TEST_CASE(test_InvalidDNSAnswer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1030,7 +1030,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileWriting, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1118,7 +1118,7 @@ BOOST_FIXTURE_TEST_CASE(test_TimeoutWhileReading, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1205,7 +1205,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortWrite, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1299,7 +1299,7 @@ BOOST_FIXTURE_TEST_CASE(test_ShortRead, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1385,7 +1385,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileReading, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1479,7 +1479,7 @@ BOOST_FIXTURE_TEST_CASE(test_ConnectionClosedWhileWriting, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1590,7 +1590,7 @@ BOOST_FIXTURE_TEST_CASE(test_GoAwayFromServer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1683,7 +1683,7 @@ BOOST_FIXTURE_TEST_CASE(test_HTTP500FromServer, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1784,7 +1784,7 @@ BOOST_FIXTURE_TEST_CASE(test_WrongStreamID, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_ProxyProtocol, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   tlsCtx->d_needProxyProtocol = true;
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
index cf6d5050b4cd9228f275e1865e0db1daf82acbc0..63cda50e2ff87fe4d5569faa3138602425a6994d 100644 (file)
@@ -561,7 +561,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_SelfAnswered, TestFixture)
 {
   const auto tcpRecvTimeout = dnsdist::configuration::getCurrentRuntimeConfiguration().d_tcpRecvTimeout;
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -823,7 +823,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered, T
 {
   const auto tcpRecvTimeout = dnsdist::configuration::getCurrentRuntimeConfiguration().d_tcpRecvTimeout;
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -967,7 +967,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionWithProxyProtocol_SelfAnswered, T
 BOOST_FIXTURE_TEST_CASE(test_IncomingConnection_BackendNoOOOR, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
@@ -1939,7 +1939,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendOOOR, TestFixture)
 {
   const auto tcpRecvTimeout = dnsdist::configuration::getCurrentRuntimeConfiguration().d_tcpRecvTimeout;
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   /* enable out-of-order on the front side */
   localCS.d_maxInFlightQueriesPerConn = 65536;
 
@@ -4179,7 +4179,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR, TestFixture)
 {
   const auto tcpRecvTimeout = dnsdist::configuration::getCurrentRuntimeConfiguration().d_tcpRecvTimeout;
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   /* enable out-of-order on the front side */
   localCS.d_maxInFlightQueriesPerConn = 65536;
 
@@ -4472,7 +4472,7 @@ BOOST_FIXTURE_TEST_CASE(test_IncomingConnectionOOOR_BackendNotOOOR, TestFixture)
 BOOST_FIXTURE_TEST_CASE(test_Pipelined_Queries_Immediate_Responses, TestFixture)
 {
   auto local = getBackendAddress("1", 80);
-  ClientState localCS(local, true, false, 0, "", {}, true);
+  ClientState localCS(local, true, false, 0, "", {}, true, false);
   auto tlsCtx = std::make_shared<MockupTLSCtx>();
   localCS.tlsFrontend = std::make_shared<TLSFrontend>(tlsCtx);
 
diff --git a/regression-tests.common/paddingoption.py b/regression-tests.common/paddingoption.py
new file mode 100644 (file)
index 0000000..bb3382e
--- /dev/null
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+import dns
+import dns.edns
+import dns.flags
+import dns.message
+import dns.query
+
+
+class PaddingOption(dns.edns.Option):
+    """Implementation of rfc7830."""
+
+    def __init__(self, numberOfBytes):
+        super(PaddingOption, self).__init__(12)
+        self.numberOfBytes = numberOfBytes
+
+    def to_wire(self, file=None):
+        """Create EDNS packet as defined in rfc7830."""
+
+        if not file:
+            return bytes(self.numberOfBytes)
+        file.write(bytes(self.numberOfBytes))
+        return None
+
+    def from_wire(cls, otype, wire, current, olen):
+        """Read EDNS packet as defined in rfc7830.
+
+        Returns:
+            An instance of PaddingOption based on the EDNS packet
+        """
+
+        numberOfBytes = olen
+
+        return cls(numberOfBytes)
+
+    from_wire = classmethod(from_wire)
+
+    # needed in 2.0.0
+    @classmethod
+    def from_wire_parser(cls, otype, parser):
+        data = parser.get_remaining()
+        return cls(len(data))
+
+    def __repr__(self):
+        return "%s(%d)" % (self.__class__.__name__, self.numberOfBytes)
+
+    def to_text(self):
+        return self.__repr__()
+
+    def __eq__(self, other):
+        if not isinstance(other, PaddingOption):
+            return False
+        return self.numberOfBytes == numberOfBytes
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+dns.edns._type_to_class[0x000C] = PaddingOption
index b54bccc7774c3b00a25d598f04279f78d95910e3..f08cd9ed10c374a938e8e8e176a51fbeb099f669 100644 (file)
@@ -13,6 +13,7 @@ import pycurl
 import clientsubnetoption
 from dnsdistdohtests import DNSDistDOHTest
 from dnsdisttests import DNSDistTest, pickAvailablePort
+import paddingoption
 
 
 class DOHTests(object):
@@ -2052,3 +2053,89 @@ class DOHXFR(object):
 
 class TestDOHXFRNGHTTP2(DOHXFR, DNSDistDOHTest):
     _dohLibrary = "nghttp2"
+
+
+class DOHEDNSPadding(object):
+    _serverKey = "server.key"
+    _serverCert = "server.chain"
+    _serverName = "tls.tests.dnsdist.org"
+    _caCert = "ca.pem"
+    _dohServerPort = pickAvailablePort()
+    _dohBaseURL = "https://%s:%d/" % (_serverName, _dohServerPort)
+    _config_template = """
+    newServer{address="127.0.0.1:%d"}
+
+    addDOHLocal("127.0.0.1:%d", "%s", "%s", { "/" }, {padResponses=true, library='%s'})
+    """
+    _config_params = ["_testServerPort", "_dohServerPort", "_serverCert", "_serverKey", "_dohLibrary"]
+
+    def testDOHWithPadding(self):
+        """
+        DOH with EDNS Padding
+        """
+        name = "padded.doh.tests.powerdns.com."
+        po = paddingoption.PaddingOption(64)
+        query = dns.message.make_query(name, "A", "IN", use_edns=True, options=[po])
+
+        (_, receivedResponse) = self.sendDOHQuery(
+            self._dohServerPort,
+            self._serverName,
+            self._dohBaseURL,
+            query,
+            caFile=self._caCert,
+        )
+        self.assertEqual(len(receivedResponse.to_wire()) % 468, 0)
+        self.assertTrue(receivedResponse)
+        self.assertEqual(receivedResponse.edns, 0)
+        self.assertEqual(len(receivedResponse.options), 1)
+        for option in receivedResponse.options:
+            self.assertEqual(option.otype, 12)
+
+    def testDOHWithPaddedResponse(self):
+        """
+        DOH with EDNS Padding, with already padded response
+        """
+        name = "paddedresponse.doh.tests.powerdns.com."
+        po = paddingoption.PaddingOption(64)
+        query = dns.message.make_query(name, "A", "IN", use_edns=True, options=[po])
+        response = dns.message.make_response(query, pad=128)
+        rrset = dns.rrset.from_text(name, 3600, dns.rdataclass.IN, dns.rdatatype.A, "127.0.0.1")
+        response.answer.append(rrset)
+
+        (_, receivedResponse) = self.sendDOHQuery(
+            self._dohServerPort,
+            self._serverName,
+            self._dohBaseURL,
+            query,
+            response=response,
+            caFile=self._caCert,
+        )
+        self.assertEqual(len(receivedResponse.to_wire()) % 128, 0)
+        self.assertNotEqual(len(receivedResponse.to_wire()) % 468, 0)
+        self.assertTrue(receivedResponse)
+        self.assertEqual(receivedResponse.edns, 0)
+        self.assertEqual(len(receivedResponse.options), 1)
+        for option in receivedResponse.options:
+            self.assertEqual(option.otype, 12)
+
+    def testDOHWithoutEDNS(self):
+        """
+        DOH without EDNS in query
+        """
+        name = "no.edns.doh.tests.powerdns.com."
+        query = dns.message.make_query(name, "A", "IN")
+
+        (_, receivedResponse) = self.sendDOHQuery(
+            self._dohServerPort,
+            self._serverName,
+            self._dohBaseURL,
+            query,
+            caFile=self._caCert,
+        )
+        self.assertTrue(receivedResponse)
+        self.assertNotEqual(receivedResponse.edns, 0)
+        self.assertEqual(len(receivedResponse.options), 0)
+
+
+class TestDOHEDNSPadding(DOHEDNSPadding, DNSDistDOHTest):
+    _dohLibrary = "nghttp2"
deleted file mode 100644 (file)
index bb3382e1a129442a1abb5e4bc13e17d1aacf8de6..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python
-
-import dns
-import dns.edns
-import dns.flags
-import dns.message
-import dns.query
-
-
-class PaddingOption(dns.edns.Option):
-    """Implementation of rfc7830."""
-
-    def __init__(self, numberOfBytes):
-        super(PaddingOption, self).__init__(12)
-        self.numberOfBytes = numberOfBytes
-
-    def to_wire(self, file=None):
-        """Create EDNS packet as defined in rfc7830."""
-
-        if not file:
-            return bytes(self.numberOfBytes)
-        file.write(bytes(self.numberOfBytes))
-        return None
-
-    def from_wire(cls, otype, wire, current, olen):
-        """Read EDNS packet as defined in rfc7830.
-
-        Returns:
-            An instance of PaddingOption based on the EDNS packet
-        """
-
-        numberOfBytes = olen
-
-        return cls(numberOfBytes)
-
-    from_wire = classmethod(from_wire)
-
-    # needed in 2.0.0
-    @classmethod
-    def from_wire_parser(cls, otype, parser):
-        data = parser.get_remaining()
-        return cls(len(data))
-
-    def __repr__(self):
-        return "%s(%d)" % (self.__class__.__name__, self.numberOfBytes)
-
-    def to_text(self):
-        return self.__repr__()
-
-    def __eq__(self, other):
-        if not isinstance(other, PaddingOption):
-            return False
-        return self.numberOfBytes == numberOfBytes
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-
-dns.edns._type_to_class[0x000C] = PaddingOption
new file mode 120000 (symlink)
index 0000000000000000000000000000000000000000..eba474cf3d7ad61d15f966671212e0d62e5547a3
--- /dev/null
@@ -0,0 +1 @@
+../regression-tests.common/paddingoption.py
\ No newline at end of file