From: Chris Hofstaedtler Date: Sun, 26 Nov 2017 19:30:24 +0000 (+0100) Subject: Recursor: add ecs-add-for option X-Git-Tag: dnsdist-1.3.0~152^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2fe3354ddb44a2fa1a674c94d3b93a28a7ad629f;p=thirdparty%2Fpdns.git Recursor: add ecs-add-for option --- diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index caa8ba45a9..374e6dbd93 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -167,6 +167,7 @@ uint16_t g_outgoingEDNSBufsize; bool g_logRPZChanges{false}; #define LOCAL_NETS "127.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 169.254.0.0/16, 192.168.0.0/16, 172.16.0.0/12, ::1/128, fc00::/7, fe80::/10" +#define LOCAL_NETS_INVERSE "!127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10" // Bad Nets taken from both: // http://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml // and @@ -789,7 +790,6 @@ static void startDoResolve(void *p) if(t_pdl) { sr.setLuaEngine(t_pdl); } - sr.d_requestor=dc->d_remote; // ECS needs this too if(g_dnssecmode != DNSSECMode::Off) { sr.setDoDNSSEC(true); @@ -808,12 +808,7 @@ static void startDoResolve(void *p) sr.setInitialRequestId(dc->d_uuid); #endif - if (g_useIncomingECS) { - sr.setIncomingECSFound(dc->d_ecsFound); - if (dc->d_ecsFound) { - sr.setIncomingECS(dc->d_ednssubnet); - } - } + sr.setQuerySource(dc->d_remote, g_useIncomingECS && !dc->d_ednssubnet.source.empty() ? boost::optional(dc->d_ednssubnet) : boost::none); bool tracedQuery=false; // we could consider letting Lua know about this too bool variableAnswer = false; @@ -2990,6 +2985,10 @@ static int serviceMain(int argc, char*argv[]) } } + SyncRes::parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]); + SyncRes::parseEDNSSubnetAddFor(::arg()["ecs-add-for"]); + g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet"); + g_networkTimeoutMsec = ::arg().asNum("network-timeout"); g_initialDomainMap = parseAuthAndForwards(); @@ -3034,9 +3033,6 @@ static int serviceMain(int argc, char*argv[]) makeTCPServerSockets(0); } - SyncRes::parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]); - g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet"); - int forks; for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) { if(!fork()) // we are child @@ -3394,6 +3390,7 @@ int main(int argc, char **argv) ::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("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")=""; ::arg().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="no"; ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="yes"; diff --git a/pdns/recursordist/docs/settings.rst b/pdns/recursordist/docs/settings.rst index 8940d8eb65..0af9166038 100644 --- a/pdns/recursordist/docs/settings.rst +++ b/pdns/recursordist/docs/settings.rst @@ -322,6 +322,20 @@ This setting can be used to expand or reduce the limitations. Queries to addresses for zones as configured in any of the settings `forward-zones`_, `forward-zones-file`_ or `forward-zones-recurse`_ are performed regardless of these limitations. +.. _setting-ecs-add-for: + +``ecs-add-for`` +-------------------------- +.. versionadded:: 4.2.0 + +- Comma separated list of netmasks +- Default: 0.0.0.0/0, ::, !127.0.0.0/8, !10.0.0.0/8, !100.64.0.0/10, !169.254.0.0/16, !192.168.0.0/16, !172.16.0.0/12, !::1/128, !fc00::/7, !fe80::/10 + +List of requestor netmasks for which the requestor IP Address should be used as the :rfc:`EDNS Client Subnet <7871>` for outgoing queries. Instead, `ecs-scope-zero-address`_ would be used. +Valid incoming ECS values from `use-incoming-edns-subnet`_ are not replaced. + +This defaults to not using the requestor address inside RFC1918 and similar "private" IP address spaces. + .. _setting-ecs-ipv4-bits: ``ecs-ipv4-bits`` @@ -378,8 +392,10 @@ Lower this if you experience timeouts. - Default: (none) List of netmasks and domains that :rfc:`EDNS Client Subnet <7871>` should be enabled for in outgoing queries. -For example, an EDNS Client Subnet option containing the address of the initial requestor will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains. -The initial requestor address will be truncated to 24 bits for IPv4 and to 56 bits for IPv6, as recommended in the privacy section of RFC 7871. + +For example, an EDNS Client Subnet option containing the address of the initial requestor (but see `ecs-add-for`_) will be added to an outgoing query sent to server 192.0.2.1 for domain X if 192.0.2.1 matches one of the supplied netmasks, or if X matches one of the supplied domains. +The initial requestor address will be truncated to 24 bits for IPv4 (see `ecs-ipv4-bits`_) and to 56 bits for IPv6 (see `ecs-ipv6-bits`_), as recommended in the privacy section of RFC 7871. + By default, this option is empty, meaning no EDNS Client Subnet information is sent. .. _setting-entropy-source: diff --git a/pdns/recursordist/test-syncres_cc.cc b/pdns/recursordist/test-syncres_cc.cc index d768415087..7900ca22ae 100644 --- a/pdns/recursordist/test-syncres_cc.cc +++ b/pdns/recursordist/test-syncres_cc.cc @@ -133,10 +133,14 @@ static void init(bool debug=false) SyncRes::s_rootNXTrust = true; SyncRes::s_minimumTTL = 0; SyncRes::s_serverID = "PowerDNS Unit Tests Server ID"; - SyncRes::clearEDNSSubnets(); + SyncRes::clearEDNSLocalSubnets(); + SyncRes::addEDNSLocalSubnet("0.0.0.0/0"); + SyncRes::addEDNSLocalSubnet("::/0"); + SyncRes::clearEDNSRemoteSubnets(); SyncRes::clearEDNSDomains(); SyncRes::clearDelegationOnly(); SyncRes::clearDontQuery(); + SyncRes::setECSScopeZeroAddress(Netmask("127.0.0.1/32")); SyncRes::clearNSSpeeds(); BOOST_CHECK_EQUAL(SyncRes::getNSSpeedsSize(), 0); @@ -1037,8 +1041,7 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_domain) { EDNSSubnetOpts incomingECS; incomingECS.source = Netmask("192.0.2.128/32"); - sr->setIncomingECSFound(true); - sr->setIncomingECS(incomingECS); + sr->setQuerySource(ComboAddress(), boost::optional(incomingECS)); sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { @@ -1059,12 +1062,11 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) { primeHints(); const DNSName target("powerdns.com."); - SyncRes::addEDNSSubnet(Netmask("192.0.2.1/32")); + SyncRes::addEDNSRemoteSubnet("192.0.2.1/32"); EDNSSubnetOpts incomingECS; incomingECS.source = Netmask("2001:DB8::FF/128"); - sr->setIncomingECSFound(true); - sr->setIncomingECS(incomingECS); + sr->setQuerySource(ComboAddress(), boost::optional(incomingECS)); sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { @@ -1096,6 +1098,178 @@ BOOST_AUTO_TEST_CASE(test_edns_submask_by_addr) { BOOST_CHECK_EQUAL(ret[0].d_name, target); } +BOOST_AUTO_TEST_CASE(test_ecs_use_requestor) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSRemoteSubnet("192.0.2.1/32"); + // No incoming ECS data + sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + BOOST_REQUIRE(!srcmask); + + 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, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.2.0/24"); + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + 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(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_ecs_use_scope_zero) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSRemoteSubnet("192.0.2.1/32"); + SyncRes::clearEDNSLocalSubnets(); + SyncRes::addEDNSLocalSubnet("192.0.2.254/32"); + // No incoming ECS data, Requestor IP not in ecs-add-for + sr->setQuerySource(ComboAddress("192.0.2.127"), boost::none); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + BOOST_REQUIRE(!srcmask); + + 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, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32"); + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + 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(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSRemoteSubnet("192.0.2.1/32"); + SyncRes::clearEDNSLocalSubnets(); + SyncRes::addEDNSLocalSubnet("192.0.2.254/32"); + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("192.0.0.0/16"); + sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional(incomingECS)); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + BOOST_REQUIRE(!srcmask); + + 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, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "192.0.0.0/16"); + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + 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(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + +BOOST_AUTO_TEST_CASE(test_ecs_honor_incoming_mask_zero) { + std::unique_ptr sr; + initSR(sr); + + primeHints(); + + const DNSName target("powerdns.com."); + SyncRes::addEDNSRemoteSubnet("192.0.2.1/32"); + SyncRes::clearEDNSLocalSubnets(); + SyncRes::addEDNSLocalSubnet("192.0.2.254/32"); + EDNSSubnetOpts incomingECS; + incomingECS.source = Netmask("0.0.0.0/0"); + sr->setQuerySource(ComboAddress("192.0.2.127"), boost::optional(incomingECS)); + + sr->setAsyncCallback([target](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { + + if (isRootServer(ip)) { + BOOST_REQUIRE(!srcmask); + + 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, "192.0.2.1", DNSResourceRecord::ADDITIONAL, 3600); + return 1; + } else if (ip == ComboAddress("192.0.2.1:53")) { + + BOOST_REQUIRE(srcmask); + BOOST_CHECK_EQUAL(srcmask->toString(), "127.0.0.1/32"); + + setLWResult(res, 0, true, false, false); + addRecordToLW(res, domain, QType::A, "192.0.2.2"); + return 1; + } + + return 0; + }); + + 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(ret[0].d_type == QType::A); + BOOST_CHECK_EQUAL(ret[0].d_name, target); +} + BOOST_AUTO_TEST_CASE(test_following_cname) { std::unique_ptr sr; initSR(sr); @@ -1784,8 +1958,7 @@ BOOST_AUTO_TEST_CASE(test_skip_negcache_for_variable_response) { EDNSSubnetOpts incomingECS; incomingECS.source = Netmask("192.0.2.128/32"); - sr->setIncomingECSFound(true); - sr->setIncomingECS(incomingECS); + sr->setQuerySource(ComboAddress(), boost::optional(incomingECS)); sr->setAsyncCallback([target,cnameTarget](const ComboAddress& ip, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, int EDNS0Level, struct timeval* now, boost::optional& srcmask, boost::optional context, std::shared_ptr outgoingLogger, LWResult* res) { diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 3738396ae2..4f4fc7d8d6 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -40,7 +40,8 @@ thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage; std::unordered_set SyncRes::s_delegationOnly; std::unique_ptr SyncRes::s_dontQuery{nullptr}; -NetmaskGroup SyncRes::s_ednssubnets; +NetmaskGroup SyncRes::s_ednslocalsubnets; +NetmaskGroup SyncRes::s_ednsremotesubnets; SuffixMatchNode SyncRes::s_ednsdomains; EDNSSubnetOpts SyncRes::s_ecsScopeZero; string SyncRes::s_serverID; @@ -687,7 +688,7 @@ vector SyncRes::getAddrs(const DNSName &qname, unsigned int depth, if(done) { if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around vector cset; - if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) { + if(t_RC->get(d_now.tv_sec, qname, QType(QType::AAAA), false, &cset, d_cacheRemote) > 0) { for(auto k=cset.cbegin();k!=cset.cend();++k) { if(k->d_ttl > (unsigned int)d_now.tv_sec ) { if (auto drc = getRR(*k)) { @@ -760,7 +761,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto vector ns; *flawedNSSet = false; - if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 0) { + if(t_RC->get(d_now.tv_sec, subdomain, QType(QType::NS), false, &ns, d_cacheRemote) > 0) { for(auto k=ns.cbegin();k!=ns.cend(); ++k) { if(k->d_ttl > (unsigned int)d_now.tv_sec ) { vector aset; @@ -768,7 +769,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto const DNSRecord& dr=*k; auto nrr = getRR(dr); if(nrr && (!nrr->getNS().isPartOf(subdomain) || t_RC->get(d_now.tv_sec, nrr->getNS(), s_doIPv6 ? QType(QType::ADDR) : QType(QType::A), - false, doLog() ? &aset : 0, d_incomingECSFound ? d_incomingECSNetwork : d_requestor) > 5)) { + false, doLog() ? &aset : 0, d_cacheRemote) > 5)) { bestns.push_back(dr); LOG(prefix< '"<getNS()<<"'"<getNS().isPartOf(subdomain)); @@ -894,7 +895,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector vector> signatures; vector> authorityRecs; bool wasAuth; - if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) { + if(t_RC->get(d_now.tv_sec, qname, QType(QType::CNAME), d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &state, &wasAuth) > 0) { for(auto j=cset.cbegin() ; j != cset.cend() ; ++j) { if(j->d_ttl>(unsigned int) d_now.tv_sec) { @@ -916,7 +917,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures); if (state != Indeterminate) { LOG(prefix<updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, state); + t_RC->updateValidationStatus(d_now.tv_sec, qname, QType(QType::CNAME), d_cacheRemote, d_requireAuthData, state); } } } @@ -1143,7 +1144,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w vector> authorityRecs; uint32_t ttl=0; bool wasCachedAuth; - if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) { + if(t_RC->get(d_now.tv_sec, sqname, sqt, d_requireAuthData, &cset, d_cacheRemote, d_doDNSSEC ? &signatures : nullptr, d_doDNSSEC ? &authorityRecs : nullptr, &d_wasVariable, &cachedState, &wasCachedAuth) > 0) { LOG(prefix<updateValidationStatus(d_now.tv_sec, sqname, sqt, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, d_requireAuthData, cachedState); + t_RC->updateValidationStatus(d_now.tv_sec, sqname, sqt, d_cacheRemote, d_requireAuthData, cachedState); } } @@ -2235,7 +2236,7 @@ bool SyncRes::processRecords(const std::string& prefix, const DNSName& qname, co updateValidationState(state, st); /* we already stored the record with a different validation status, let's fix it */ - t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_incomingECSFound ? d_incomingECSNetwork : d_requestor, lwr.d_aabit, st); + t_RC->updateValidationStatus(d_now.tv_sec, qname, qtype, d_cacheRemote, lwr.d_aabit, st); } } } @@ -2352,7 +2353,7 @@ bool SyncRes::doResolveAtThisIP(const std::string& prefix, const DNSName& qname, LOG(prefix<toString()<<" to query"< incomingECS) +void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional incomingECS) { - d_incomingECS = incomingECS; - if (incomingECS) { - if (d_incomingECS->source.getBits() == 0) { + d_requestor = requestor; + + if (incomingECS && incomingECS->source.getBits() > 0) { + d_cacheRemote = incomingECS->source.getMaskedNetwork(); + uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIpv4() ? s_ecsipv4limit : s_ecsipv6limit)); + ComboAddress trunc = incomingECS->source.getNetwork(); + trunc.truncate(bits); + d_outgoingECSNetwork = boost::optional(Netmask(trunc, bits)); + } else { + d_cacheRemote = d_requestor; + if(!incomingECS && s_ednslocalsubnets.match(d_requestor)) { + ComboAddress trunc = d_requestor; + uint8_t bits = d_requestor.isIPv4() ? 32 : 128; + bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit)); + trunc.truncate(bits); + d_outgoingECSNetwork = boost::optional(Netmask(trunc, bits)); + } else if (s_ecsScopeZero.source.getBits() > 0) { /* RFC7871 says we MUST NOT send any ECS if the source scope is 0. But using an empty ECS in that case would mean inserting a non ECS-specific entry into the cache, preventing any further @@ -2745,45 +2760,21 @@ void SyncRes::setIncomingECS(boost::optional incomingECS) indicator of the applicable scope. Subsequent Stub Resolver queries for /0 can then be answered from this cached response. */ - d_incomingECS = s_ecsScopeZero; - d_incomingECSNetwork = s_ecsScopeZero.source.getMaskedNetwork(); - } - else { - uint8_t bits = std::min(incomingECS->source.getBits(), (incomingECS->source.isIpv4() ? s_ecsipv4limit : s_ecsipv6limit)); - d_incomingECS->source = Netmask(incomingECS->source.getNetwork(), bits); - d_incomingECSNetwork = d_incomingECS->source.getMaskedNetwork(); + d_outgoingECSNetwork = boost::optional(s_ecsScopeZero.source.getMaskedNetwork()); + d_cacheRemote = s_ecsScopeZero.source.getNetwork(); + } else { + // ECS disabled because no scope-zero address could be derived. + d_outgoingECSNetwork = boost::none; } } - else { - d_incomingECSNetwork = ComboAddress(); - } } -boost::optional SyncRes::getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem) +boost::optional SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem) { - boost::optional result; - ComboAddress trunc; - uint8_t bits; - if(d_incomingECSFound) { - trunc = d_incomingECSNetwork; - bits = d_incomingECS->source.getBits(); - } - else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor' - trunc = local; - bits = local.isIPv4() ? 32 : 128; - bits = std::min(bits, (trunc.isIPv4() ? s_ecsipv4limit : s_ecsipv6limit)); - } - else { - /* nothing usable */ - return result; + if(d_outgoingECSNetwork && (s_ednsdomains.check(dn) || s_ednsremotesubnets.match(rem))) { + return d_outgoingECSNetwork; } - - if(s_ednsdomains.check(dn) || s_ednssubnets.match(rem)) { - trunc.truncate(bits); - return boost::optional(Netmask(trunc, bits)); - } - - return result; + return boost::none; } void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist) @@ -2792,7 +2783,7 @@ void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist) stringtok(parts, wlist, ",; "); for(const auto& a : parts) { try { - s_ednssubnets.addMask(Netmask(a)); + s_ednsremotesubnets.addMask(Netmask(a)); } catch(...) { s_ednsdomains.add(DNSName(a)); @@ -2800,6 +2791,15 @@ void SyncRes::parseEDNSSubnetWhitelist(const std::string& wlist) } } +void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist) +{ + vector parts; + stringtok(parts, subnetlist, ",; "); + for(const auto& a : parts) { + s_ednslocalsubnets.addMask(a); + } +} + // used by PowerDNSLua - note that this neglects to add the packet count & statistics back to pdns_ercursor.cc int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector& ret) { diff --git a/pdns/syncres.hh b/pdns/syncres.hh index ecfed9e278..a85e893290 100644 --- a/pdns/syncres.hh +++ b/pdns/syncres.hh @@ -421,17 +421,26 @@ public: s_dontQuery = nullptr; } static void parseEDNSSubnetWhitelist(const std::string& wlist); - static void addEDNSSubnet(const Netmask& subnet) + static void parseEDNSSubnetAddFor(const std::string& subnetlist); + static void addEDNSLocalSubnet(const std::string& subnet) { - s_ednssubnets.addMask(subnet); + s_ednslocalsubnets.addMask(subnet); + } + static void addEDNSRemoteSubnet(const std::string& subnet) + { + s_ednsremotesubnets.addMask(subnet); } static void addEDNSDomain(const DNSName& domain) { s_ednsdomains.add(domain); } - static void clearEDNSSubnets() + static void clearEDNSLocalSubnets() { - s_ednssubnets.clear(); + s_ednslocalsubnets.clear(); + } + static void clearEDNSRemoteSubnets() + { + s_ednsremotesubnets.clear(); } static void clearEDNSDomains() { @@ -603,11 +612,6 @@ public: return d_wantsRPZ; } - void setIncomingECSFound(bool state=true) - { - d_incomingECSFound=state; - } - string getTrace() const { return d_trace.str(); @@ -638,7 +642,7 @@ public: d_skipCNAMECheck = skip; } - void setIncomingECS(boost::optional incomingECS); + void setQuerySource(const ComboAddress& requestor, boost::optional incomingECS); #ifdef HAVE_PROTOBUF void setInitialRequestId(boost::optional initialRequestId) @@ -699,12 +703,14 @@ public: unsigned int d_timeouts; unsigned int d_unreachables; unsigned int d_totUsec; - ComboAddress d_requestor; private: + ComboAddress d_requestor; + ComboAddress d_cacheRemote; static std::unordered_set s_delegationOnly; - static NetmaskGroup s_ednssubnets; + static NetmaskGroup s_ednslocalsubnets; + static NetmaskGroup s_ednsremotesubnets; static SuffixMatchNode s_ednsdomains; static EDNSSubnetOpts s_ecsScopeZero; static LogMode s_lm; @@ -754,7 +760,7 @@ private: int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional& srcmask, LWResult* res) const; - boost::optional getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem); + boost::optional getEDNSSubnetMask(const DNSName&dn, const ComboAddress& rem); bool validationEnabled() const; uint32_t computeLowestTTD(const std::vector& records, const std::vector >& signatures, uint32_t signaturesTTL) const; @@ -780,8 +786,7 @@ private: zonesStates_t d_cutStates; ostringstream d_trace; shared_ptr d_pdl; - boost::optional d_incomingECS; - ComboAddress d_incomingECSNetwork; + boost::optional d_outgoingECSNetwork; #ifdef HAVE_PROTOBUF boost::optional d_initialRequestId; #endif @@ -797,7 +802,6 @@ private: bool d_doDNSSEC; bool d_DNSSECValidationRequested{false}; bool d_doEDNS0{true}; - bool d_incomingECSFound{false}; bool d_requireAuthData{true}; bool d_skipCNAMECheck{false}; bool d_updatingRootNS{false}; diff --git a/regression-tests.recursor-dnssec/test_ECS.py b/regression-tests.recursor-dnssec/test_ECS.py index f4d351db65..7e0fbfcdfc 100644 --- a/regression-tests.recursor-dnssec/test_ECS.py +++ b/regression-tests.recursor-dnssec/test_ECS.py @@ -19,6 +19,7 @@ class ECSTest(RecursorTest): daemon=no trace=yes dont-query= +ecs-add-for=0.0.0.0/0 local-address=127.0.0.1 packetcache-ttl=0 packetcache-servfail-ttl=0