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
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);
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<const EDNSSubnetOpts&>(dc->d_ednssubnet) : boost::none);
bool tracedQuery=false; // we could consider letting Lua know about this too
bool variableAnswer = false;
}
}
+ 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();
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
::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";
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``
- 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:
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);
EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
- sr->setIncomingECSFound(true);
- sr->setIncomingECS(incomingECS);
+ sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
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) {
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<const EDNSSubnetOpts&>(incomingECS));
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) {
BOOST_CHECK_EQUAL(ret[0].d_name, target);
}
+BOOST_AUTO_TEST_CASE(test_ecs_use_requestor) {
+ std::unique_ptr<SyncRes> 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<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> 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<DNSRecord> 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<SyncRes> 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<Netmask>& srcmask, boost::optional<const ResolveContext&> context, std::shared_ptr<RemoteLogger> 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<DNSRecord> 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<SyncRes> 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<const EDNSSubnetOpts&>(incomingECS));
+
+ 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) {
+
+ 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<DNSRecord> 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<SyncRes> 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<const EDNSSubnetOpts&>(incomingECS));
+
+ 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) {
+
+ 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<DNSRecord> 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<SyncRes> sr;
initSR(sr);
EDNSSubnetOpts incomingECS;
incomingECS.source = Netmask("192.0.2.128/32");
- sr->setIncomingECSFound(true);
- sr->setIncomingECS(incomingECS);
+ sr->setQuerySource(ComboAddress(), boost::optional<const EDNSSubnetOpts&>(incomingECS));
sr->setAsyncCallback([target,cnameTarget](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) {
std::unordered_set<DNSName> SyncRes::s_delegationOnly;
std::unique_ptr<NetmaskGroup> 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;
if(done) {
if(j==1 && s_doIPv6) { // we got an A record, see if we have some AAAA lying around
vector<DNSRecord> 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<AAAARecordContent>(*k)) {
vector<DNSRecord> 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<DNSRecord> aset;
const DNSRecord& dr=*k;
auto nrr = getRR<NSRecordContent>(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<<qname<<": NS (with ip, or non-glue) in cache for '"<<subdomain<<"' -> '"<<nrr->getNS()<<"'"<<endl);
LOG(prefix<<qname<<": within bailiwick: "<< nrr->getNS().isPartOf(subdomain));
vector<std::shared_ptr<RRSIGRecordContent>> signatures;
vector<std::shared_ptr<DNSRecord>> 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) {
state = SyncRes::validateRecordsWithSigs(depth, qname, QType(QType::CNAME), qname, cset, signatures);
if (state != Indeterminate) {
LOG(prefix<<qname<<": got Indeterminate state from the CNAME cache, new validation result is "<<vStates[state]<<endl);
- t_RC->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);
}
}
}
vector<std::shared_ptr<DNSRecord>> 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<<sqname<<": Found cache hit for "<<sqt.getName()<<": ");
if (cachedState != Indeterminate) {
LOG(prefix<<qname<<": got Indeterminate state from the cache, validation result is "<<vStates[cachedState]<<endl);
- t_RC->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);
}
}
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);
}
}
}
LOG(prefix<<qname<<": query handled by Lua"<<endl);
}
else {
- ednsmask=getEDNSSubnetMask(d_requestor, qname, remoteIP);
+ ednsmask=getEDNSSubnetMask(qname, remoteIP);
if(ednsmask) {
LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
s_ecsqueries++;
return -1;
}
-void SyncRes::setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS)
+void SyncRes::setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> 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>(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>(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
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<Netmask>(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<Netmask> SyncRes::getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+boost::optional<Netmask> SyncRes::getEDNSSubnetMask(const DNSName& dn, const ComboAddress& rem)
{
- boost::optional<Netmask> 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>(Netmask(trunc, bits));
- }
-
- return result;
+ return boost::none;
}
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));
}
}
+void SyncRes::parseEDNSSubnetAddFor(const std::string& subnetlist)
+{
+ vector<string> 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<DNSRecord>& ret)
{
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()
{
return d_wantsRPZ;
}
- void setIncomingECSFound(bool state=true)
- {
- d_incomingECSFound=state;
- }
-
string getTrace() const
{
return d_trace.str();
d_skipCNAMECheck = skip;
}
- void setIncomingECS(boost::optional<const EDNSSubnetOpts&> incomingECS);
+ void setQuerySource(const ComboAddress& requestor, boost::optional<const EDNSSubnetOpts&> incomingECS);
#ifdef HAVE_PROTOBUF
void setInitialRequestId(boost::optional<const boost::uuids::uuid&> initialRequestId)
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<DNSName> 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;
int asyncresolveWrapper(const ComboAddress& ip, bool ednsMANDATORY, const DNSName& domain, int type, bool doTCP, bool sendRDQuery, struct timeval* now, boost::optional<Netmask>& srcmask, LWResult* res) const;
- boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+ boost::optional<Netmask> getEDNSSubnetMask(const DNSName&dn, const ComboAddress& rem);
bool validationEnabled() const;
uint32_t computeLowestTTD(const std::vector<DNSRecord>& records, const std::vector<std::shared_ptr<RRSIGRecordContent> >& signatures, uint32_t signaturesTTL) const;
zonesStates_t d_cutStates;
ostringstream d_trace;
shared_ptr<RecursorLua4> d_pdl;
- boost::optional<EDNSSubnetOpts> d_incomingECS;
- ComboAddress d_incomingECSNetwork;
+ boost::optional<Netmask> d_outgoingECSNetwork;
#ifdef HAVE_PROTOBUF
boost::optional<const boost::uuids::uuid&> d_initialRequestId;
#endif
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};
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