Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EDNS0 padding support in auth #15009

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
1 change: 1 addition & 0 deletions modules/remotebackend/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
4 changes: 4 additions & 0 deletions pdns/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down Expand Up @@ -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 \
Expand Down
23 changes: 23 additions & 0 deletions pdns/dnspacket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -449,6 +467,7 @@ std::unique_ptr<DNSPacket> 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;
Expand Down Expand Up @@ -600,6 +619,7 @@ try
d_haveednssection = false;
d_haveednscookie = false;
d_ednscookievalid = false;
d_ednspadding = false;

if(getEDNSOpts(mdp, &edo)) {
d_haveednssection=true;
Expand Down Expand Up @@ -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 #"<<iter->first<<": "<<makeHexDump(iter->second)<<endl;
}
Expand Down
1 change: 1 addition & 0 deletions pdns/dnspacket.hh
Original file line number Diff line number Diff line change
Expand Up @@ -204,5 +204,6 @@ private:
bool d_haveednscookie{false};
bool d_ednscookievalid{false};
bool d_haveednssection{false};
bool d_ednspadding{false};
bool d_isQuery;
};
7 changes: 7 additions & 0 deletions pdns/ednspadding.hh
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,10 @@
#include <string>

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;
}
10 changes: 3 additions & 7 deletions pdns/recursordist/lwres.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
11 changes: 2 additions & 9 deletions pdns/recursordist/pdns_recursor.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
1 change: 1 addition & 0 deletions regression-tests.auth-py/paddingoption.py
75 changes: 75 additions & 0 deletions regression-tests.auth-py/test_EDNSPadding.py
Original file line number Diff line number Diff line change
@@ -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)
59 changes: 59 additions & 0 deletions regression-tests.common/paddingoption.py
Original file line number Diff line number Diff line change
@@ -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
59 changes: 0 additions & 59 deletions regression-tests.recursor-dnssec/paddingoption.py

This file was deleted.

1 change: 1 addition & 0 deletions regression-tests.recursor-dnssec/paddingoption.py
Loading