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

Backport Rec ecs cache limit with tt (7631) #7651

Merged
merged 14 commits into from Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions pdns/pdns_recursor.cc
Expand Up @@ -3145,6 +3145,9 @@ static int serviceMain(int argc, char*argv[])

SyncRes::s_ecsipv4limit = ::arg().asNum("ecs-ipv4-bits");
SyncRes::s_ecsipv6limit = ::arg().asNum("ecs-ipv6-bits");
SyncRes::s_ecsipv4cachelimit = ::arg().asNum("ecs-ipv4-cache-bits");
SyncRes::s_ecsipv6cachelimit = ::arg().asNum("ecs-ipv6-cache-bits");
SyncRes::s_ecscachelimitttl = ::arg().asNum("ecs-cache-limit-ttl");

if (!::arg().isEmpty("ecs-scope-zero-address")) {
ComboAddress scopeZero(::arg()["ecs-scope-zero-address"]);
Expand Down Expand Up @@ -3613,7 +3616,10 @@ int main(int argc, char **argv)
::arg().set("latency-statistic-size","Number of latency values to calculate the qa-latency average")="10000";
::arg().setSwitch( "disable-packetcache", "Disable packetcache" )= "no";
::arg().set("ecs-ipv4-bits", "Number of bits of IPv4 address to pass for EDNS Client Subnet")="24";
::arg().set("ecs-ipv4-cache-bits", "Maximum number of bits of IPv4 mask to cache ECS response")="24";
::arg().set("ecs-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56";
::arg().set("ecs-ipv6-cache-bits", "Maximum number of bits of IPv6 mask to cache ECS response")="56";
::arg().set("ecs-cache-limit-ttl", "Minimum TTL to cache ECS response")="0";
::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for")="";
::arg().set("ecs-scope-zero-address", "Address to send to whitelisted authoritative servers for incoming queries with ECS prefix-length source of 0")="";
::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="no";
Expand Down
47 changes: 47 additions & 0 deletions pdns/recursordist/docs/settings.rst
Expand Up @@ -352,6 +352,18 @@ Queries to addresses for zones as configured in any of the settings `forward-zon

Number of bits of client IPv4 address to pass when sending EDNS Client Subnet address information.

.. _setting-ecs-ipv4-cache-bits:

``ecs-ipv4-cache-bits``
-----------------
.. versionadded:: 4.1.12

- Integer
- Default: 24

Maximum number of bits of client IPv4 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
That is, only if both the limits apply, the record will not be cached.

.. _setting-ecs-ipv6-bits:

``ecs-ipv6-bits``
Expand All @@ -363,6 +375,41 @@ Number of bits of client IPv4 address to pass when sending EDNS Client Subnet ad

Number of bits of client IPv6 address to pass when sending EDNS Client Subnet address information.

.. _setting-ecs-ipv6-cache-bits:

``ecs-ipv6-cache-bits``
-----------------
.. versionadded:: 4.1.12

- Integer
- Default: 56

Maximum number of bits of client IPv6 address used by the authoritative server (as indicated by the EDNS Client Subnet scope in the answer) for an answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-cache-limit-ttl``.
That is, only if both the limits apply, the record will not be cached.

.. _setting-ecs-minimum-ttl-override:

``ecs-minimum-ttl-override``
----------------------------
- Integer
- Default: 0 (disabled)

This setting artificially raises the TTLs of records in the ANSWER section of ECS-specific answers to be at least this long.
While this is a gross hack, and violates RFCs, under conditions of DoS, it may enable you to continue serving your customers.
Can be set at runtime using ``rec_control set-ecs-minimum-ttl 3600``.

.. _setting-ecs-cache-limit-ttl:

``ecs-cache-limit-ttl``
-----------------------
.. versionadded:: 4.1.12

- Integer
- Default: 0 (disabled)

The minimum TTL for an ECS-specific answer to be inserted into the query cache. This condition applies in conjunction with ``ecs-ipv4-cache-bits`` or ``ecs-ipv6-cache-bits``.
That is, only if both the limits apply, the record will not be cached.

.. _setting-ecs-scope-zero-address:

``ecs-scope-zero-address``
Expand Down
203 changes: 203 additions & 0 deletions pdns/recursordist/test-syncres_cc.cc
Expand Up @@ -130,6 +130,9 @@ static void init(bool debug=false)
SyncRes::s_doIPv6 = true;
SyncRes::s_ecsipv4limit = 24;
SyncRes::s_ecsipv6limit = 56;
SyncRes::s_ecsipv4cachelimit = 24;
SyncRes::s_ecsipv6cachelimit = 56;
SyncRes::s_ecscachelimitttl = 0;
SyncRes::s_rootNXTrust = true;
SyncRes::s_minimumTTL = 0;
SyncRes::s_serverID = "PowerDNS Unit Tests Server ID";
Expand Down Expand Up @@ -1877,6 +1880,8 @@ BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) {
addRecordToLW(res, "powerdns.com.", QType::NS, "pdns-public-ns1.powerdns.com.", DNSResourceRecord::AUTHORITY, 172800);
addRecordToLW(res, "pdns-public-ns1.powerdns.com.", QType::A, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600);

srcmask = boost::none;

return 1;
} else if (ip == ComboAddress("192.0.2.1:53")) {
if (domain == target) {
Expand Down Expand Up @@ -1906,6 +1911,204 @@ BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) {
BOOST_CHECK_EQUAL(SyncRes::getNegCacheSize(), 0);
}

BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_allowed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);

primeHints();

const DNSName target("www.powerdns.com.");

SyncRes::addEDNSDomain(DNSName("powerdns.com."));

EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecsipv4cachelimit = 24;

sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res, bool* chained) {

BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");

setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");

return 1;
});

const time_t now = sr->getNow().tv_sec;
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(ret.size(), 1);

/* should have been cached */
const ComboAddress who("192.0.2.128");
vector<DNSRecord> cached;
BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
BOOST_REQUIRE_EQUAL(cached.size(), 1);
}

BOOST_AUTO_TEST_CASE(test_ecs_cache_limit_no_ttl_limit_allowed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);

primeHints();

const DNSName target("www.powerdns.com.");

SyncRes::addEDNSDomain(DNSName("powerdns.com."));

EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecsipv4cachelimit = 16;

sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res, bool* chained) {

BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");

setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");

return 1;
});

const time_t now = sr->getNow().tv_sec;
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(ret.size(), 1);

/* should have been cached because /24 is more specific than /16 but TTL limit is nof effective */
const ComboAddress who("192.0.2.128");
vector<DNSRecord> cached;
BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
BOOST_REQUIRE_EQUAL(cached.size(), 1);
}

BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_allowed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);

primeHints();

const DNSName target("www.powerdns.com.");

SyncRes::addEDNSDomain(DNSName("powerdns.com."));

EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecscachelimitttl = 30;

sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res, bool* chained) {

BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");

setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");

return 1;
});

const time_t now = sr->getNow().tv_sec;
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(ret.size(), 1);

/* should have been cached */
const ComboAddress who("192.0.2.128");
vector<DNSRecord> cached;
BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
BOOST_REQUIRE_EQUAL(cached.size(), 1);
}

BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_and_scope_allowed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);

primeHints();

const DNSName target("www.powerdns.com.");

SyncRes::addEDNSDomain(DNSName("powerdns.com."));

EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecscachelimitttl = 100;
SyncRes::s_ecsipv4cachelimit = 24;

sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res, bool* chained) {

BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");

setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");

return 1;
});

const time_t now = sr->getNow().tv_sec;
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(ret.size(), 1);

/* should have been cached */
const ComboAddress who("192.0.2.128");
vector<DNSRecord> cached;
BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
BOOST_REQUIRE_EQUAL(cached.size(), 1);
}

BOOST_AUTO_TEST_CASE(test_ecs_cache_ttllimit_notallowed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);

primeHints();

const DNSName target("www.powerdns.com.");

SyncRes::addEDNSDomain(DNSName("powerdns.com."));

EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
SyncRes::s_ecscachelimitttl = 100;
SyncRes::s_ecsipv4cachelimit = 16;

sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> outgoingLogger, LWResult* res, bool* chained) {

BOOST_REQUIRE(srcmask);
BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24");

setLWResult(res, 0, true, false, true);
addRecordToLW(res, target, QType::A, "192.0.2.1");

return 1;
});

const time_t now = sr->getNow().tv_sec;
vector<DNSRecord> ret;
int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret);
BOOST_CHECK_EQUAL(res, RCode::NoError);
BOOST_CHECK_EQUAL(ret.size(), 1);

/* should have NOT been cached because TTL of 60 is too small and /24 is more specific than /16 */
const ComboAddress who("192.0.2.128");
vector<DNSRecord> cached;
BOOST_REQUIRE_LT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0);
BOOST_REQUIRE_EQUAL(cached.size(), 0);
}


BOOST_AUTO_TEST_CASE(test_ns_speed) {
std::unique_ptr<SyncRes> sr;
initSR(sr);
Expand Down
33 changes: 31 additions & 2 deletions pdns/syncres.cc
Expand Up @@ -56,6 +56,7 @@ unsigned int SyncRes::s_packetcachettl;
unsigned int SyncRes::s_packetcacheservfailttl;
unsigned int SyncRes::s_serverdownmaxfails;
unsigned int SyncRes::s_serverdownthrottletime;
unsigned int SyncRes::s_ecscachelimitttl;
std::atomic<uint64_t> SyncRes::s_authzonequeries;
std::atomic<uint64_t> SyncRes::s_queries;
std::atomic<uint64_t> SyncRes::s_outgoingtimeouts;
Expand All @@ -71,6 +72,9 @@ std::atomic<uint64_t> SyncRes::s_ecsqueries;
std::atomic<uint64_t> SyncRes::s_ecsresponses;
uint8_t SyncRes::s_ecsipv4limit;
uint8_t SyncRes::s_ecsipv6limit;
uint8_t SyncRes::s_ecsipv4cachelimit;
uint8_t SyncRes::s_ecsipv6cachelimit;

bool SyncRes::s_doIPv6;
bool SyncRes::s_nopacketcache;
bool SyncRes::s_rootNXTrust;
Expand Down Expand Up @@ -2136,8 +2140,32 @@ RCode::rcodes_ SyncRes::updateCacheFromRecords(unsigned int depth, LWResult& lwr
- denial of existence proofs in wildcard expanded positive responses are stored in authorityRecs
- denial of existence proofs for negative responses are stored in the negative cache
*/
if (i->first.type != QType::NSEC3) {
t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
if (i->first.type != QType::NSEC3 && (i->first.type == QType::DS || i->first.type == QType::NS || i->first.type == QType::A || i->first.type == QType::AAAA || isAA || wasForwardRecurse)) {
omoerbeek marked this conversation as resolved.
Show resolved Hide resolved

bool doCache = true;
if (i->first.place == DNSResourceRecord::ANSWER && ednsmask) {
// If ednsmask is relevant, we do not want to cache if the scope prefix length is large and TTL is small
if (SyncRes::s_ecscachelimitttl > 0) {
bool manyMaskBits = (ednsmask->isIpv4() && ednsmask->getBits() > SyncRes::s_ecsipv4cachelimit) ||
(ednsmask->isIpv6() && ednsmask->getBits() > SyncRes::s_ecsipv6cachelimit);

if (manyMaskBits) {
uint32_t minttl = UINT32_MAX;
for (const auto &it : i->second.records) {
if (it.d_ttl < minttl)
minttl = it.d_ttl;
}
bool ttlIsSmall = minttl < SyncRes::s_ecscachelimitttl + d_now.tv_sec;
if (ttlIsSmall) {
// Case: many bits and ttlIsSmall
doCache = false;
}
}
}
}
if (doCache) {
t_RC->replace(d_now.tv_sec, i->first.name, QType(i->first.type), i->second.records, i->second.signatures, authorityRecs, i->first.type == QType::DS ? true : isAA, i->first.place == DNSResourceRecord::ANSWER ? ednsmask : boost::none, recordState);
}
}

if(i->first.place == DNSResourceRecord::ANSWER && ednsmask)
Expand Down Expand Up @@ -2899,3 +2927,4 @@ int SyncRes::getRootNS(struct timeval now, asyncresolve_t asyncCallback) {

return res;
}

9 changes: 9 additions & 0 deletions pdns/syncres.hh
Expand Up @@ -639,6 +639,12 @@ public:
d_skipCNAMECheck = skip;
}

void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS) {
omoerbeek marked this conversation as resolved.
Show resolved Hide resolved

setIncomingECSFound();
setIncomingECS(incomingECS);
}

void setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS);

#ifdef HAVE_PROTOBUF
Expand Down Expand Up @@ -685,8 +691,11 @@ public:
static unsigned int s_packetcacheservfailttl;
static unsigned int s_serverdownmaxfails;
static unsigned int s_serverdownthrottletime;
static unsigned int s_ecscachelimitttl;
static uint8_t s_ecsipv4limit;
static uint8_t s_ecsipv6limit;
static uint8_t s_ecsipv4cachelimit;
static uint8_t s_ecsipv6cachelimit;
static bool s_doIPv6;
static bool s_noEDNSPing;
static bool s_noEDNS;
Expand Down