From: Remi Gacogne Date: Wed, 13 Mar 2019 14:39:17 +0000 (+0100) Subject: rec: Add a new ecs-minimum-ttl-override setting X-Git-Tag: dnsdist-1.4.0-alpha1~41^2~2 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5cf4b2e7a143a1344a05ff4c51c4804e372cfb6c;p=thirdparty%2Fpdns.git rec: Add a new ecs-minimum-ttl-override setting --- diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index ceb5f42d74..8715b3ba87 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -3630,6 +3630,7 @@ static int serviceMain(int argc, char*argv[]) } SyncRes::s_minimumTTL = ::arg().asNum("minimum-ttl-override"); + SyncRes::s_minimumECSTTL = ::arg().asNum("ecs-minimum-ttl-override"); SyncRes::s_nopacketcache = ::arg().mustDo("disable-packetcache"); @@ -4262,6 +4263,7 @@ int main(int argc, char **argv) ::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-ipv6-bits", "Number of bits of IPv6 address to pass for EDNS Client Subnet")="56"; + ::arg().set("ecs-minimum-ttl-override", "Set under adverse conditions, a minimum TTL for records in ECS-specific answers")="0"; ::arg().set("edns-subnet-whitelist", "List of netmasks and domains that we should enable EDNS subnet for")=""; ::arg().set("ecs-add-for", "List of client netmasks for which EDNS Client Subnet will be added")="0.0.0.0/0, ::/0, " LOCAL_NETS_INVERSE; ::arg().set("ecs-scope-zero-address", "Address to send to whitelisted authoritative servers for incoming queries with ECS prefix-length source of 0")=""; diff --git a/pdns/rec_channel_rec.cc b/pdns/rec_channel_rec.cc index 4b101b638a..75bdc2e5a0 100644 --- a/pdns/rec_channel_rec.cc +++ b/pdns/rec_channel_rec.cc @@ -694,12 +694,21 @@ static string getTAs() template static string setMinimumTTL(T begin, T end) { - if(end-begin != 1) + if(end-begin != 1) return "Need to supply new minimum TTL number\n"; SyncRes::s_minimumTTL = pdns_stou(*begin); return "New minimum TTL: " + std::to_string(SyncRes::s_minimumTTL) + "\n"; } +template +static string setMinimumECSTTL(T begin, T end) +{ + if(end-begin != 1) + return "Need to supply new ECS minimum TTL number\n"; + SyncRes::s_minimumECSTTL = pdns_stou(*begin); + return "New minimum ECS TTL: " + std::to_string(SyncRes::s_minimumECSTTL) + "\n"; +} + template static string setMaxCacheEntries(T begin, T end) { @@ -1362,6 +1371,7 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP "reload-lua-script [filename] (re)load Lua script\n" "reload-lua-config [filename] (re)load Lua configuration file\n" "reload-zones reload all auth and forward zones\n" +"set-ecs-minimum-ttl value set ecs-minimum-ttl-override\n" "set-max-cache-entries value set new maximum cache size\n" "set-max-packetcache-entries val set new maximum packet cache size\n" "set-minimum-ttl value set minimum-ttl-override\n" @@ -1532,6 +1542,10 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP return reloadAuthAndForwards(); } + if(cmd=="set-ecs-minimum-ttl") { + return setMinimumECSTTL(begin, end); + } + if(cmd=="set-max-cache-entries") { return setMaxCacheEntries(begin, end); } diff --git a/pdns/recursordist/docs/manpages/rec_control.1.rst b/pdns/recursordist/docs/manpages/rec_control.1.rst index d6b8dfe804..8fa4b018cd 100644 --- a/pdns/recursordist/docs/manpages/rec_control.1.rst +++ b/pdns/recursordist/docs/manpages/rec_control.1.rst @@ -196,6 +196,9 @@ set-dnssec-log-bogus *SETTING* DNSSEC validation failures and to 'no' or 'off' to disable logging these failures. +set-ecs-minimum-ttl *NUM* + Set ecs-minimum-ttl-override to *NUM*. + set-max-cache-entries *NUM* Change the maximum number of entries in the DNS cache. If reduced, the cache size will start shrinking to this number as part of the normal diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index c74a4ca548..5d9b529bfb 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -388,6 +388,17 @@ 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-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-scope-zero-address: ``ecs-scope-zero-address`` diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index f66682bacf..67876a2727 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -132,6 +132,7 @@ static void init(bool debug=false) SyncRes::s_ecsipv6limit = 56; SyncRes::s_rootNXTrust = true; SyncRes::s_minimumTTL = 0; + SyncRes::s_minimumECSTTL = 0; SyncRes::s_serverID = "PowerDNS Unit Tests Server ID"; SyncRes::clearEDNSLocalSubnets(); SyncRes::addEDNSLocalSubnet("0.0.0.0/0"); @@ -2346,6 +2347,75 @@ BOOST_AUTO_TEST_CASE(test_cache_min_max_ttl) { BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl); } +BOOST_AUTO_TEST_CASE(test_cache_min_max_ecs_ttl) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("cacheecsttl.powerdns.com."); + const ComboAddress ns("192.0.2.1:53"); + + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("192.0.2.128/32"); + sr->setQuerySource(ComboAddress(), boost::optional(incomingECS)); + SyncRes::addEDNSDomain(target); + + sr->setAsyncCallback([target,ns](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, LWResult* res, bool* chained) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24"); + + if (isRootServer(ip)) { + + setLWResult(res, 0, false, false, true); + addRecordToLW(res, domain, QType::NS, "a.gtld-servers.net.", DNSResourceRecord::AUTHORITY, 172800); + addRecordToLW(res, "a.gtld-servers.net.", QType::A, ns.toString(), DNSResourceRecord::ADDITIONAL, 20); + srcmask = boost::none; + + return 1; + } else if (ip == ns) { + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2", DNSResourceRecord::ANSWER, 10); + + return 1; + } + + return 0; + }); + + const time_t now = sr->getNow().tv_sec; + SyncRes::s_minimumTTL = 60; + SyncRes::s_minimumECSTTL = 120; + SyncRes::s_maxcachettl = 3600; + + vector ret; + int res = sr->beginResolve(target, QType(QType::A), QClass::IN, ret); + BOOST_CHECK_EQUAL(res, RCode::NoError); + BOOST_REQUIRE_EQUAL(ret.size(), 1); + BOOST_CHECK_EQUAL(ret[0].d_ttl, SyncRes::s_minimumECSTTL); + + const ComboAddress who("192.0.2.128"); + vector cached; + BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::A), true, &cached, who), 0); + BOOST_REQUIRE_EQUAL(cached.size(), 1); + BOOST_REQUIRE_GT(cached[0].d_ttl, now); + BOOST_CHECK_EQUAL((cached[0].d_ttl - now), SyncRes::s_minimumECSTTL); + + cached.clear(); + BOOST_REQUIRE_GT(t_RC->get(now, target, QType(QType::NS), false, &cached, who), 0); + BOOST_REQUIRE_EQUAL(cached.size(), 1); + BOOST_REQUIRE_GT(cached[0].d_ttl, now); + BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_maxcachettl); + + cached.clear(); + BOOST_REQUIRE_GT(t_RC->get(now, DNSName("a.gtld-servers.net."), QType(QType::A), false, &cached, who), 0); + BOOST_REQUIRE_EQUAL(cached.size(), 1); + BOOST_REQUIRE_GT(cached[0].d_ttl, now); + BOOST_CHECK_LE((cached[0].d_ttl - now), SyncRes::s_minimumTTL); +} + BOOST_AUTO_TEST_CASE(test_cache_expired_ttl) { std::unique_ptr sr; initSR(sr); diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 933d32c682..7686b4f677 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -55,6 +55,7 @@ unsigned int SyncRes::s_maxqperq; unsigned int SyncRes::s_maxtotusec; unsigned int SyncRes::s_maxdepth; unsigned int SyncRes::s_minimumTTL; +unsigned int SyncRes::s_minimumECSTTL; unsigned int SyncRes::s_packetcachettl; unsigned int SyncRes::s_packetcacheservfailttl; unsigned int SyncRes::s_serverdownmaxfails; @@ -2817,6 +2818,16 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn } } + /* if the answer is ECS-specific, a minimum TTL is set for this kind of answers + and it's higher than the global minimum TTL */ + if (ednsmask && s_minimumECSTTL > 0 && (s_minimumTTL == 0 || s_minimumECSTTL > s_minimumTTL)) { + for(auto& rec : lwr.d_records) { + if (rec.d_place == DNSResourceRecord::ANSWER) { + rec.d_ttl = max(rec.d_ttl, s_minimumECSTTL); + } + } + } + bool needWildcardProof = false; unsigned int wildcardLabelsCount; *rcode = updateCacheFromRecords(depth, lwr, qname, qtype, auth, wasForwarded, ednsmask, state, needWildcardProof, wildcardLabelsCount, sendRDQuery); diff --git a/pdns/syncres.hh b/pdns/syncres.hh index f177f47b5b..d808668b49 100644 --- a/pdns/syncres.hh +++ b/pdns/syncres.hh @@ -705,6 +705,7 @@ public: static string s_serverID; static unsigned int s_minimumTTL; + static unsigned int s_minimumECSTTL; static unsigned int s_maxqperq; static unsigned int s_maxtotusec; static unsigned int s_maxdepth;