diff --git a/meson.build b/meson.build index c726bf8eb77c..08dafc1722af 100644 --- a/meson.build +++ b/meson.build @@ -540,6 +540,8 @@ common_sources += files( src_dir / 'ednscookies.hh', src_dir / 'ednsoptions.cc', src_dir / 'ednsoptions.hh', + src_dir / 'ednspadding.cc', + src_dir / 'ednspadding.hh', src_dir / 'ednssubnet.cc', src_dir / 'ednssubnet.hh', src_dir / 'gss_context.cc', diff --git a/modules/remotebackend/Makefile.am b/modules/remotebackend/Makefile.am index a342a87cf63c..c16ac043fbbe 100644 --- a/modules/remotebackend/Makefile.am +++ b/modules/remotebackend/Makefile.am @@ -126,6 +126,7 @@ libtestremotebackend_la_SOURCES = \ ../../pdns/dnswriter.cc \ ../../pdns/ednscookies.cc \ ../../pdns/ednsoptions.cc ../../pdns/ednsoptions.hh \ + ../../pdns/ednspadding.cc ../../pdns/ednspadding.hh \ ../../pdns/ednssubnet.cc \ ../../pdns/gss_context.cc ../../pdns/gss_context.hh \ ../../pdns/iputils.cc \ diff --git a/pdns/Makefile.am b/pdns/Makefile.am index 042ad78769b5..2510f8b20dd5 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -230,6 +230,7 @@ pdns_server_SOURCES = \ dynmessenger.hh \ ednscookies.cc ednscookies.hh \ ednsoptions.cc ednsoptions.hh \ + ednspadding.cc ednspadding.hh \ ednssubnet.cc ednssubnet.hh \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ @@ -360,6 +361,7 @@ pdnsutil_SOURCES = \ dynlistener.cc \ ednscookies.cc ednscookies.hh \ ednsoptions.cc ednsoptions.hh \ + ednspadding.cc ednspadding.hh \ ednssubnet.cc \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ @@ -722,6 +724,7 @@ ixfrdist_SOURCES = \ ednscookies.cc ednscookies.hh \ ednsextendederror.cc ednsextendederror.hh \ ednsoptions.cc ednsoptions.hh \ + ednspadding.cc ednspadding.hh \ ednssubnet.cc ednssubnet.hh \ gss_context.cc gss_context.hh \ iputils.hh iputils.cc \ @@ -1355,6 +1358,7 @@ testrunner_SOURCES = \ dnswriter.cc \ ednscookies.cc ednscookies.hh \ ednsoptions.cc ednsoptions.hh \ + ednspadding.cc ednspadding.hh \ ednssubnet.cc \ gettime.cc gettime.hh \ gss_context.cc gss_context.hh \ diff --git a/pdns/dnspacket.cc b/pdns/dnspacket.cc index 8588f2c21a15..5f17861d73a9 100644 --- a/pdns/dnspacket.cc +++ b/pdns/dnspacket.cc @@ -39,6 +39,7 @@ #include "dnsbackend.hh" #include "ednsoptions.hh" #include "ednscookies.hh" +#include "ednspadding.hh" #include "pdnsexception.hh" #include "dnspacket.hh" #include "logger.hh" @@ -334,6 +335,11 @@ void DNSPacket::wrapup(bool throwsOnTruncation) } } + if (d_ednspadding) { + // actual padding length not included yet + optsize += EDNS_OPTION_CODE_SIZE + EDNS_OPTION_LENGTH_SIZE; + } + if (d_trc.d_algoName.countLabels()) { // TSIG is not OPT, but we count it in optsize anyway @@ -383,6 +389,18 @@ void DNSPacket::wrapup(bool throwsOnTruncation) opts.emplace_back(EDNSOptionCode::COOKIE, d_eco.makeOptString()); } + if (d_ednspadding) { + size_t remaining = d_tcp ? 65535 : getMaxReplyLen(); + // Note that optsize already contains the size of the EDNS0 padding + // option header. + size_t modulo = (pw.size() + optsize) % rfc8467::serverPaddingBlockSize; + size_t padSize = 0; + if (modulo > 0) { + padSize = std::min(rfc8467::serverPaddingBlockSize - modulo, remaining); + } + opts.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)); + } + if(!opts.empty() || d_haveednssection || d_dnssecOk) { pw.addOpt(s_udpTruncationThreshold, d_ednsrcode, d_dnssecOk ? EDNSOpts::DNSSECOK : 0, opts); @@ -449,6 +467,7 @@ std::unique_ptr DNSPacket::replyPacket() const r->d_haveednssubnet = d_haveednssubnet; r->d_haveednssection = d_haveednssection; r->d_haveednscookie = d_haveednscookie; + r->d_ednspadding = d_ednspadding; r->d_ednsversion = 0; r->d_ednsrcode = 0; r->d_xfr = d_xfr; @@ -600,6 +619,7 @@ try d_haveednssection = false; d_haveednscookie = false; d_ednscookievalid = false; + d_ednspadding = false; if(getEDNSOpts(mdp, &edo)) { d_haveednssection=true; @@ -627,6 +647,9 @@ try d_eco.makeFromString(option.second); d_ednscookievalid = d_eco.isValid(s_EDNSCookieKey, d_remote); } + else if (option.first == EDNSOptionCode::PADDING) { + d_ednspadding = true; + } else { // cerr<<"Have an option #"<first<<": "<second)< std::string makeEDNSPaddingOptString(size_t bytes); + +namespace rfc8467 +{ +// Constants from RFC8467 4.1 "Recommended Strategy: Block-Length Padding" +constexpr size_t clientPaddingBlockSize = 128; +constexpr size_t serverPaddingBlockSize = 468; +} diff --git a/pdns/recursordist/lwres.cc b/pdns/recursordist/lwres.cc index 95bdb00f5278..eb2f78d45a7c 100644 --- a/pdns/recursordist/lwres.cc +++ b/pdns/recursordist/lwres.cc @@ -373,15 +373,11 @@ static void addPadding(const DNSPacketWriter& pw, size_t bufsize, DNSPacketWrite const size_t currentSize = pw.getSizeWithOpts(opts); if (currentSize < (bufsize - 4)) { const size_t remaining = bufsize - (currentSize + 4); - /* from rfc8647, "4.1. Recommended Strategy: Block-Length Padding": - Clients SHOULD pad queries to the closest multiple of 128 octets. - Note we are in the client role here. - */ - const size_t blockSize = 128; - const size_t modulo = (currentSize + 4) % blockSize; + // Note we are in the client role here. + const size_t modulo = (currentSize + 4) % rfc8467::clientPaddingBlockSize; size_t padSize = 0; if (modulo > 0) { - padSize = std::min(blockSize - modulo, remaining); + padSize = std::min(rfc8467::clientPaddingBlockSize - modulo, remaining); } opts.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)); } diff --git a/pdns/recursordist/pdns_recursor.cc b/pdns/recursordist/pdns_recursor.cc index 8f022abb8c45..b0afacfcc5b0 100644 --- a/pdns/recursordist/pdns_recursor.cc +++ b/pdns/recursordist/pdns_recursor.cc @@ -1623,17 +1623,10 @@ void startDoResolve(void* arg) // NOLINT(readability-function-cognitive-complexi if (currentSize < (maxSize - 4)) { size_t remaining = maxSize - (currentSize + 4); - /* 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). - */ - const size_t blockSize = 468; - size_t modulo = (currentSize + 4) % blockSize; + size_t modulo = (currentSize + 4) % rfc8467::serverPaddingBlockSize; size_t padSize = 0; if (modulo > 0) { - padSize = std::min(blockSize - modulo, remaining); + padSize = std::min(rfc8467::serverPaddingBlockSize - modulo, remaining); } returnedEdnsOptions.emplace_back(EDNSOptionCode::PADDING, makeEDNSPaddingOptString(padSize)); } diff --git a/regression-tests.auth-py/paddingoption.py b/regression-tests.auth-py/paddingoption.py new file mode 120000 index 000000000000..eba474cf3d7a --- /dev/null +++ b/regression-tests.auth-py/paddingoption.py @@ -0,0 +1 @@ +../regression-tests.common/paddingoption.py \ No newline at end of file diff --git a/regression-tests.auth-py/test_EDNSPadding.py b/regression-tests.auth-py/test_EDNSPadding.py new file mode 100644 index 000000000000..13470ca8c4cc --- /dev/null +++ b/regression-tests.auth-py/test_EDNSPadding.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +import dns +import os +import socket + +import paddingoption + +from authtests import AuthTest + +class AuthEDNSPaddingTest(AuthTest): + _config_template = """ +launch=bind +""" + + _zones = { + 'example.org': """ +example.org. 3600 IN SOA {soa} +example.org. 3600 IN NS ns1.example.org. +example.org. 3600 IN NS ns2.example.org. +ns1.example.org. 3600 IN A 192.0.2.10 +ns2.example.org. 3600 IN A 192.0.2.11 + +www.example.org. 3600 IN A 192.0.2.5 + """, + } + + @classmethod + def setUpClass(cls): + cls.setUpSockets() + + cls.startResponders() + + confdir = os.path.join('configs', cls._confdir) + cls.createConfigDir(confdir) + + cls.generateAllAuthConfig(confdir) + cls.startAuth(confdir, "0.0.0.0") + + print("Launching tests..") + + @classmethod + def setUpSockets(cls): + print("Setting up UDP socket..") + cls._sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + cls._sock.settimeout(2.0) + cls._sock.connect((cls._PREFIX + ".2", cls._authPort)) + + def checkPadding(self, message): + self.assertEqual(message.edns, 0) + self.assertEqual(len(message.options), 1) + for option in message.options: + self.assertEqual(option.otype, 12) + + def checkNoEDNS(self, message): + self.assertEqual(message.edns, -1) + +class TestEDNSPadding(AuthEDNSPaddingTest): + + def testQueryWithPadding(self): + name = 'www.example.org.' + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.5') + po = paddingoption.PaddingOption(64) + query = dns.message.make_query(name, 'A', options=[po]) + res = self.sendUDPQuery(query) + self.checkPadding(res) + self.assertRRsetInAnswer(res, expected) + + def testQueryWithoutPadding(self): + name = 'www.example.org.' + expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.5') + query = dns.message.make_query(name, 'A') + res = self.sendUDPQuery(query) + self.checkNoEDNS(res) + self.assertRRsetInAnswer(res, expected) diff --git a/regression-tests.common/paddingoption.py b/regression-tests.common/paddingoption.py new file mode 100644 index 000000000000..728da93a7d09 --- /dev/null +++ b/regression-tests.common/paddingoption.py @@ -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 file: + file.write(bytes(self.numberOfBytes)) + else: + return bytes(self.numberOfBytes) + + 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 __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 diff --git a/regression-tests.recursor-dnssec/paddingoption.py b/regression-tests.recursor-dnssec/paddingoption.py deleted file mode 100644 index 728da93a7d09..000000000000 --- a/regression-tests.recursor-dnssec/paddingoption.py +++ /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 file: - file.write(bytes(self.numberOfBytes)) - else: - return bytes(self.numberOfBytes) - - 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 __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 diff --git a/regression-tests.recursor-dnssec/paddingoption.py b/regression-tests.recursor-dnssec/paddingoption.py new file mode 120000 index 000000000000..eba474cf3d7a --- /dev/null +++ b/regression-tests.recursor-dnssec/paddingoption.py @@ -0,0 +1 @@ +../regression-tests.common/paddingoption.py \ No newline at end of file