Skip to content

Commit

Permalink
rec: Implement EDNS0 padding (rfc7830)
Browse files Browse the repository at this point in the history
  • Loading branch information
rgacogne committed Mar 8, 2021
1 parent 65d5850 commit 53604c2
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 5 deletions.
16 changes: 13 additions & 3 deletions pdns/dnswriter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ template <typename Container> void GenericDNSPacketWriter<Container>::getRecordP
records.assign(d_content.begin() + d_sor, d_content.end());
}

template <typename Container> uint32_t GenericDNSPacketWriter<Container>::size()
template <typename Container> uint32_t GenericDNSPacketWriter<Container>::size() const
{
return d_content.size();
}
Expand Down Expand Up @@ -495,8 +495,18 @@ template <typename Container> void GenericDNSPacketWriter<Container>::commit()

}

template <typename Container> size_t GenericDNSPacketWriter<Container>::getSizeWithOpts(const optvect_t& options) const
{
size_t result = size() + /* root */ 1 + DNS_TYPE_SIZE + DNS_CLASS_SIZE + DNS_TTL_SIZE + DNS_RDLENGTH_SIZE;

for(auto const &option : options) {
result += 4;
result += option.second.size();
}

return result;
}

template class GenericDNSPacketWriter<std::vector<uint8_t>>;
#include "noinitvector.hh"
template class GenericDNSPacketWriter<PacketBuffer>;


5 changes: 4 additions & 1 deletion pdns/dnswriter.hh
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public:
*/
void commit();

uint32_t size(); // needs to be 32 bit because otherwise we don't see the wrap coming when it happened!
uint32_t size() const; // needs to be 32 bit because otherwise we don't see the wrap coming when it happened!

/** Should the packet have grown too big for the writer's liking, rollback removes the record currently being written */
void rollback();
Expand Down Expand Up @@ -155,6 +155,9 @@ public:
const string getRemaining() const {
return "";
}

size_t getSizeWithOpts(const optvect_t& options) const;

private:
uint16_t lookupName(const DNSName& name, uint16_t* matchlen);
vector<uint16_t> d_namepositions;
Expand Down
31 changes: 31 additions & 0 deletions pdns/ednspadding.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* This file is part of PowerDNS or dnsdist.
* Copyright -- PowerDNS.COM B.V. and its contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* In addition, for the avoidance of any doubt, permission is granted to
* link this program with OpenSSL and to (re)distribute the binaries
* produced as the result of such linking.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#include "ednspadding.hh"

std::string makeEDNSPaddingOptString(size_t bytes)
{
std::string ret;
ret.resize(bytes, '0');
return ret;
}

26 changes: 26 additions & 0 deletions pdns/ednspadding.hh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* This file is part of PowerDNS or dnsdist.
* Copyright -- PowerDNS.COM B.V. and its contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* In addition, for the avoidance of any doubt, permission is granted to
* link this program with OpenSSL and to (re)distribute the binaries
* produced as the result of such linking.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once

#include <string>

std::string makeEDNSPaddingOptString(size_t bytes);
67 changes: 66 additions & 1 deletion pdns/pdns_recursor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@
#include "rec-lua-conf.hh"
#include "ednsoptions.hh"
#include "ednsextendederror.hh"
#include "ednspadding.hh"
#include "gettime.hh"
#include "proxy-protocol.hh"
#include "pubsuffix.hh"
Expand Down Expand Up @@ -192,6 +193,7 @@ static deferredAdd_t g_deferredAdds;

typedef vector<int> tcpListenSockets_t;
typedef map<int, ComboAddress> listenSocketsAddresses_t; // is shared across all threads right now
enum class PaddingMode { Always, PaddedQueries };

static listenSocketsAddresses_t g_listenSocketsAddresses; // is shared across all threads right now
static set<int> g_fromtosockets; // listen sockets that use 'sendfromto()' mechanism (without actually using sendfromto())
Expand All @@ -200,6 +202,7 @@ static std::shared_ptr<SyncRes::domainmap_t> g_initialDomainMap; // new threads
static std::shared_ptr<NetmaskGroup> g_initialAllowFrom; // new thread needs to be setup with this
static NetmaskGroup g_XPFAcl;
static NetmaskGroup g_proxyProtocolACL;
static NetmaskGroup g_paddingFrom;
static boost::optional<ComboAddress> g_dns64Prefix{boost::none};
static DNSName g_dns64PrefixReverse;
static size_t g_proxyProtocolMaximumSize;
Expand All @@ -211,9 +214,11 @@ static unsigned int g_maxTCPPerClient;
static unsigned int g_maxMThreads;
static unsigned int g_numDistributorThreads;
static unsigned int g_numWorkerThreads;
static unsigned int g_paddingTag;
static int g_tcpTimeout;
static uint16_t g_udpTruncationThreshold;
static uint16_t g_xpfRRCode{0};
static PaddingMode g_paddingMode;
static std::atomic<bool> statsWanted;
static std::atomic<bool> g_quiet;
static bool g_logCommonErrors;
Expand Down Expand Up @@ -1484,6 +1489,8 @@ static void startDoResolve(void *p)
std::vector<pair<uint16_t, string> > ednsOpts;
bool variableAnswer = dc->d_variable;
bool haveEDNS=false;
bool paddingAllowed = g_paddingFrom.match(dc->d_remote);
bool padResponse = paddingAllowed ? (g_paddingMode == PaddingMode::Always) : false;
#ifdef NOD_ENABLED
bool hasUDR = false;
#endif /* NOD_ENABLED */
Expand Down Expand Up @@ -1515,9 +1522,24 @@ static void startDoResolve(void *p)
variableAnswer = true; // Can't packetcache an answer with NSID
maxanswersize -= EDNSOptionCodeSize + EDNSOptionLengthSize + mode_server_id.size();
}
} else if (paddingAllowed && padResponse == false && g_paddingMode == PaddingMode::PaddedQueries && o.first == EDNSOptionCode::PADDING) {
/* we should only pad on 'secure' cases to limit amplification:
- over TCP ;
- from 'known-good' sources (dnsdist for example).
We should also support a strict mode (only pad if the query has a padding option)
and a relaxed mode (pad if the client supports EDNS).
The block-size should be configurable as well.
*/
padResponse = true;
maxanswersize -= 4;
}
}
}

if (padResponse && dc->d_tag == 0) {
dc->d_tag = g_paddingTag;
}

/* perhaps there was no EDNS or no ECS but by now we looked */
dc->d_ecsParsed = true;
vector<DNSRecord> ret;
Expand Down Expand Up @@ -1968,6 +1990,26 @@ static void startDoResolve(void *p)
}
}

if (padResponse) {
size_t currentSize = pw.getSizeWithOpts(returnedEdnsOptions);
if (currentSize < (static_cast<size_t>(maxanswersize) - 4)) {
size_t remaining = static_cast<size_t>(maxanswersize) - currentSize;
/* from rfc8647, "4.1. Recommended Strategy: Block-Length Padding":
If a server receives a query that includes the EDNS(0) "Padding"
option, it MUST pad the corresponding response (see Section 4 of
RFC 7830) and SHOULD pad the corresponding response to a
multiple of 468 octets (see below).
*/
static const size_t blockSize = 468;
size_t modulo = (currentSize + 4) % blockSize;
size_t padSize = 0;
if (modulo > 0) {
padSize = std::min(blockSize - modulo, remaining);
}
returnedEdnsOptions.push_back(make_pair(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)));
}
}

if (haveEDNS) {
auto state = sr.getValidationState();
if (dc->d_extendedErrorCode || (s_addExtendedResolutionDNSErrors && vStateIsBogus(state))) {
Expand Down Expand Up @@ -2599,6 +2641,10 @@ static void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
}
}

if (dc->d_tag == 0 && g_paddingFrom.match(dc->d_remote)) {
dc->d_tag = g_paddingTag;
}

const struct dnsheader* dh = reinterpret_cast<const struct dnsheader*>(&conn->data[0]);

if (t_protobufServers || t_outgoingProtobufServers) {
Expand Down Expand Up @@ -2889,13 +2935,15 @@ static string* doProcessUDPQuestion(const std::string& question, const ComboAddr
}

RecursorPacketCache::OptPBData pbData{boost::none};

if (t_protobufServers) {
if (logQuery && !(luaconfsLocal->protobufExportConfig.taggedOnly && policyTags.empty())) {
protobufLogQuery(luaconfsLocal, uniqueId, source, destination, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags, requestorId, deviceId, deviceName);
}
}

if (ctag == 0 && g_paddingFrom.match(fromaddr)) {
ctag = g_paddingTag;
}
/* It might seem like a good idea to skip the packet cache lookup if we know that the answer is not cacheable,
but it means that the hash would not be computed. If some script decides at a later time to mark back the answer
as cacheable we would cache it with a wrong tag, so better safe than sorry. */
Expand Down Expand Up @@ -4716,6 +4764,19 @@ static int serviceMain(int argc, char*argv[])

g_lowercaseOutgoing = ::arg().mustDo("lowercase-outgoing");

g_paddingFrom.toMasks(::arg()["edns-padding-from"]);
if (::arg()["edns-padding-mode"] == "always") {
g_paddingMode = PaddingMode::Always;
}
else if (::arg()["edns-padding-mode"] == "padded-queries-only") {
g_paddingMode = PaddingMode::PaddedQueries;
}
else {
g_log << Logger::Error << "Unknown edns-padding-mode: " << ::arg()["edns-padding-mode"] << endl;
exit(1);
}
g_paddingTag = ::arg().asNum("edns-padding-tag");

g_numDistributorThreads = ::arg().asNum("distributor-threads");
g_numWorkerThreads = ::arg().asNum("threads");
if (g_numWorkerThreads < 1) {
Expand Down Expand Up @@ -5530,6 +5591,10 @@ int main(int argc, char **argv)

::arg().setSwitch("aggressive-nsec-cache-size", "The number of records to cache in the aggressive cache. If set to a value greater than 0, and DNSSEC validation is enabled, the recursor will cache NSEC and NSEC3 records to generate negative answers, as defined in rfc8198")="100000";

::arg().set("edns-padding-from", "Sources (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses")="";
::arg().set("edns-padding-mode", "Whether to add EDNS padding to all responses ('always') or only to the ones to padded queries ('padded-queries-only')")="padded-queries-only";
::arg().set("edns-padding-tag", "Packetcache tag associated to responses sent with EDNS padding, to prevent sending these to non-whitelisted clients.")="7830";

::arg().setCmd("help","Provide a helpful message");
::arg().setCmd("version","Print version string");
::arg().setCmd("config","Output blank configuration");
Expand Down
2 changes: 2 additions & 0 deletions pdns/recursordist/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ pdns_recursor_SOURCES = \
dnswriter.cc dnswriter.hh \
ednsextendederror.cc ednsextendederror.hh \
ednsoptions.cc ednsoptions.hh \
ednspadding.cc ednspadding.hh \
ednssubnet.cc ednssubnet.hh \
filterpo.cc filterpo.hh \
fstrm_logger.cc fstrm_logger.hh \
Expand Down Expand Up @@ -246,6 +247,7 @@ testrunner_SOURCES = \
ednscookies.cc ednscookies.hh \
ednsextendederror.cc ednsextendederror.hh \
ednsoptions.cc ednsoptions.hh \
ednspadding.cc ednspadding.hh \
ednssubnet.cc ednssubnet.hh \
filterpo.cc filterpo.hh \
gettime.cc gettime.hh \
Expand Down
34 changes: 34 additions & 0 deletions pdns/recursordist/docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,40 @@ found, the recursor fallbacks to sending 127.0.0.1.
This is the value set for the EDNS0 buffer size in outgoing packets.
Lower this if you experience timeouts.

.. _setting-edns-padding-from:

``edns-padding-from``
---------------------
.. versionadded:: 4.4.0

- Comma separated list of netmasks
- Default: (none)

List of netmasks (proxy IP in case of XPF or proxy-protocol presence, client IP otherwise) for which EDNS padding will be enabled in responses. See also `edns-padding-mode`_.

.. _setting-edns-padding-mode:

``edns-padding-mode``
---------------------
.. versionadded:: 4.4.0

- One of ``always``, ``padded-queries-only``, String
- Default: ``padded-queries-only``

Whether to add EDNS padding to all responses (``always``) or only to the ones for padded queries (``padded-queries-only``, the default) coming from `edns-padding-from`_ sources.

.. _setting-edns-padding-mode:

``edns-padding-tag``
--------------------
.. versionadded:: 4.4.0

- Integer
- Default: 7830

The packetcache tag to use for padded responses, to prevent a client not allowed by the `edns-padding-from`_ list to be served a cached answer generated for an allowed one. This
effectively divides the packet cache in two when `edns-padding-from`_ is used. Note that this will not override a tag set from one of the ``Lua`` hooks.

.. _setting-edns-subnet-whitelist:

``edns-subnet-whitelist``
Expand Down
1 change: 1 addition & 0 deletions pdns/recursordist/ednspadding.cc
1 change: 1 addition & 0 deletions pdns/recursordist/ednspadding.hh

0 comments on commit 53604c2

Please sign in to comment.