]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Recursor: add ecs-add-for option
authorChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Sun, 26 Nov 2017 19:30:24 +0000 (20:30 +0100)
committerChris Hofstaedtler <chris.hofstaedtler@deduktiva.com>
Sun, 10 Dec 2017 12:35:43 +0000 (13:35 +0100)
pdns/pdns_recursor.cc
pdns/recursordist/docs/settings.rst
pdns/recursordist/test-syncres_cc.cc
pdns/syncres.cc
pdns/syncres.hh
regression-tests.recursor-dnssec/test_ECS.py

index caa8ba45a9099fb7c4ac7e1f49f9da5a2b41cb4d..374e6dbd932a1e8a850ab89ade1b420cdb636a4f 100644 (file)
@@ -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<const EDNSSubnetOpts&>(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";
index 8940d8eb65b173928da1aa6b47619bee0a95640e..0af9166038ab7ed01adce3b0e48bdc109bc4e7cc 100644 (file)
@@ -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:
index d7684150879a4ecdd66567df61de8afd1c9efe39..7900ca22aecb98d4981bdf70d3d3bff3d10053bd 100644 (file)
@@ -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<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) {
 
@@ -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<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) {
 
@@ -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<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);
@@ -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<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) {
 
index 3738396ae25ae14bc3b2f52c95b6ef8952c3529b..4f4fc7d8d6424a84da7beaf48f1efbfce6d43be4 100644 (file)
@@ -40,7 +40,8 @@ thread_local SyncRes::ThreadLocalStorage SyncRes::t_sstorage;
 
 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;
@@ -687,7 +688,7 @@ vector<ComboAddress> 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<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)) {
@@ -760,7 +761,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
     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;
@@ -768,7 +769,7 @@ void SyncRes::getBestNSFromCache(const DNSName &qname, const QType& qtype, vecto
           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));
@@ -894,7 +895,7 @@ bool SyncRes::doCNAMECacheCheck(const DNSName &qname, const QType &qtype, vector
   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) {
@@ -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<<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);
             }
           }
         }
@@ -1143,7 +1144,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
   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()<<": ");
 
@@ -1170,7 +1171,7 @@ bool SyncRes::doCacheCheck(const DNSName &qname, const DNSName& authname, bool w
 
       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);
       }
     }
 
@@ -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<<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++;
@@ -2726,11 +2727,25 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
   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
@@ -2745,45 +2760,21 @@ void SyncRes::setIncomingECS(boost::optional<const EDNSSubnetOpts&> 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<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)
@@ -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<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)
 {
index ecfed9e278465e7e647f7ece22a6b61187bcfeaa..a85e893290d82b22413deff060e3740c9b641029 100644 (file)
@@ -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<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)
@@ -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<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;
@@ -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<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;
@@ -780,8 +786,7 @@ private:
   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
@@ -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};
index f4d351db650575d89e3f9d798c9e1f7dc4d48ff8..7e0fbfcdfc5be5fa0189585880a3c0d877b7790f 100644 (file)
@@ -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