]> granicus.if.org Git - pdns/commitdiff
rec: Add `use-incoming-edns-subnet` to process and pass along ECS
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 9 Feb 2017 14:01:41 +0000 (15:01 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 20 Feb 2017 16:59:06 +0000 (17:59 +0100)
If set, the recusor will process and pass along a received EDNS
Client Subnet to authoritative servers.
The ECS information will only be sent for netmasks and domains listed
in `edns-subnet-whitelist`, and will be truncated if the received scope
exceeds `ecs-ipv4-bits` for IPv4 or `ecs-ipv6-bits` for IPv6.
An incoming ECS source prefix-length of 0 can also be used to
request that no ECS value be sent to the authoritative servers,
in accordance with RFC7871.

docs/markdown/recursor/settings.md
pdns/ednssubnet.hh
pdns/lua-recursor4.cc
pdns/pdns_recursor.cc
pdns/recursordist/ecs.cc
pdns/syncres.cc
pdns/syncres.hh

index 1a039b4b7ad7878b7295935cea738c9c56489cd9..d99e3258eb60c1dde8b034035d499552d87e127f 100644 (file)
@@ -855,6 +855,15 @@ performance. Large responses however also have downsides in terms of reflection
 attacks. This setting limits the accepted size. Maximum value is 65535, but
 values above 4096 should probably not be attempted.
 
+## `use-incoming-edns-subnet`
+* Boolean
+* Default: no
+
+Whether to process and pass along a received EDNS Client Subnet to authoritative
+servers. The ECS information will only be sent for netmasks and domains listed
+in `edns-subnet-whitelist`, and will be truncated if the received scope exceeds
+`ecs-ipv4-bits` for IPv4 or `ecs-ipv6-bits` for IPv6.
+
 ## `version`
 Print version of this binary. Useful for checking which version of the PowerDNS
 recursor is installed on a system. Available since version 3.1.5.
index aee0641aed98d95b5cd712bfa1227f2833585227..0220bd0f52e76f255d83dcbfee19053bc1ffa6dd 100644 (file)
@@ -28,6 +28,8 @@
 
 extern NetmaskGroup g_ednssubnets;
 extern SuffixMatchNode g_ednsdomains;
+extern bool g_useIncomingECS;
+
 struct EDNSSubnetOpts
 {
        Netmask source;
index 42d9133750ed5854f0dc53d76d7479607cc5706c..7316173e4c21b871634706be9bfe5a329b3d88da 100644 (file)
@@ -25,7 +25,8 @@
 #include "dnsparser.hh"
 #include "syncres.hh"
 #include "namespaces.hh"
-#include "rec_channel.hh" 
+#include "rec_channel.hh"
+#include "ednsoptions.hh"
 #include "ednssubnet.hh"
 #include "filterpo.hh"
 #include <unordered_set>
@@ -208,7 +209,7 @@ boost::optional<Netmask>  RecursorLua4::DNSQuestion::getEDNSSubnet() const
 
   if(ednsOptions) {
     for(const auto& o : *ednsOptions) {
-      if(o.first==8) {
+      if(o.first==EDNSOptionCode::ECS) {
         EDNSSubnetOpts eso;
         if(getEDNSSubnetOptsFromString(o.second, &eso))
           return eso.source;
index 1b0f304011338b3dcd5776e5b999eb1cce2bd77c..a2be2192b30f3e6e6bd35a80811331fa316771b3 100644 (file)
@@ -197,8 +197,10 @@ struct DNSComboWriter {
   ComboAddress d_remote, d_local;
 #ifdef HAVE_PROTOBUF
   boost::uuids::uuid d_uuid;
-  Netmask d_ednssubnet;
 #endif
+  EDNSSubnetOpts d_ednssubnet;
+  bool d_ecsFound{false};
+  bool d_ecsParsed{false};
   bool d_tcp;
   int d_socket;
   int d_tag{0};
@@ -701,7 +703,18 @@ void startDoResolve(void *p)
        maxanswersize = min(edo.d_packetsize, g_udpTruncationThreshold);
       dc->d_ednsOpts = edo.d_options;
       haveEDNS=true;
+
+      if (g_useIncomingECS && !dc->d_ecsParsed) {
+        for (const auto& o : edo.d_options) {
+          if (o.first == EDNSOptionCode::ECS) {
+            dc->d_ecsFound = getEDNSSubnetOptsFromString(o.second, &dc->d_ednssubnet);
+            break;
+          }
+        }
+      }
     }
+    /* perhaps there was no EDNS or no ECS but by now we looked */
+    dc->d_ecsParsed = true;
     vector<DNSRecord> ret;
     vector<uint8_t> packet;
 
@@ -714,7 +727,7 @@ void startDoResolve(void *p)
       Netmask requestorNM(dc->d_remote, dc->d_remote.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
       const ComboAddress& requestor = requestorNM.getMaskedNetwork();
       pbMessage.update(dc->d_uuid, &requestor, &dc->d_local, dc->d_tcp, dc->d_mdp.d_header.id);
-      pbMessage.setEDNSSubnet(dc->d_ednssubnet, dc->d_ednssubnet.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+      pbMessage.setEDNSSubnet(dc->d_ednssubnet.source, dc->d_ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
       pbMessage.setQuestion(dc->d_mdp.d_qname, dc->d_mdp.d_qtype, dc->d_mdp.d_qclass);
     }
 #endif /* HAVE_PROTOBUF */
@@ -757,6 +770,12 @@ void startDoResolve(void *p)
 #ifdef HAVE_PROTOBUF
     sr.d_initialRequestId = dc->d_uuid;
 #endif
+    if (g_useIncomingECS) {
+      sr.d_incomingECSFound = dc->d_ecsFound;
+      if (dc->d_ecsFound) {
+        sr.d_incomingECS = dc->d_ednssubnet;
+      }
+    }
 
     bool tracedQuery=false; // we could consider letting Lua know about this too
     bool variableAnswer = false;
@@ -793,11 +812,9 @@ void startDoResolve(void *p)
     if(!g_quiet || tracedQuery) {
       L<<Logger::Warning<<t_id<<" ["<<MT->getTid()<<"/"<<MT->numProcesses()<<"] " << (dc->d_tcp ? "TCP " : "") << "question for '"<<dc->d_mdp.d_qname<<"|"
        <<DNSRecordContent::NumberToType(dc->d_mdp.d_qtype)<<"' from "<<dc->getRemote();
-#ifdef HAVE_PROTOBUF
-      if(!dc->d_ednssubnet.empty()) {
-        L<<" (ecs "<<dc->d_ednssubnet.toString()<<")";
+      if(!dc->d_ednssubnet.source.empty()) {
+        L<<" (ecs "<<dc->d_ednssubnet.source.toString()<<")";
       }
-#endif
       L<<endl;
     }
 
@@ -1282,8 +1299,9 @@ void makeControlChannelSocket(int processNum=-1)
   }
 }
 
-static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, Netmask* ednssubnet)
+static bool getQNameAndSubnet(const std::string& question, DNSName* dnsname, uint16_t* qtype, uint16_t* qclass, EDNSSubnetOpts* ednssubnet)
 {
+  bool found = false;
   const struct dnsheader* dh = (struct dnsheader*)question.c_str();
   size_t questionLen = question.length();
   unsigned int consumed=0;
@@ -1301,11 +1319,13 @@ static void getQNameAndSubnet(const std::string& question, DNSName* dnsname, uin
       if (res == 0 && ecsLen > 4) {
         EDNSSubnetOpts eso;
         if(getEDNSSubnetOptsFromString(ecsStart + 4, ecsLen - 4, &eso)) {
-          *ednssubnet=eso.source;
+          *ednssubnet=eso;
+          found = true;
         }
       }
     }
   }
+  return found;
 }
 
 void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
@@ -1371,7 +1391,6 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       socklen_t len = dest.getSocklen();
       getsockname(conn->getFD(), (sockaddr*)&dest, &len); // if this fails, we're ok with it
       dc->setLocal(dest);
-      Netmask ednssubnet;
       DNSName qname;
       uint16_t qtype=0;
       uint16_t qclass=0;
@@ -1386,11 +1405,12 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
 
         try {
-          getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &ednssubnet);
+          dc->d_ecsParsed = true;
+          dc->d_ecsFound = getQNameAndSubnet(std::string(conn->data, conn->qlen), &qname, &qtype, &qclass, &dc->d_ednssubnet);
 
           if(t_pdl->get() && (*t_pdl)->d_gettag) {
             try {
-              dc->d_tag = (*t_pdl)->gettag(conn->d_remote, ednssubnet, dest, qname, qtype, &dc->d_policyTags, dc->d_data);
+              dc->d_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags, dc->d_data);
             }
             catch(std::exception& e)  {
               if(g_logCommonErrors)
@@ -1412,10 +1432,9 @@ void handleRunningTCPQuestion(int fd, FDMultiplexer::funcparam_t& var)
       if(luaconfsLocal->protobufServer) {
         try {
           const struct dnsheader* dh = (const struct dnsheader*) conn->data;
-          dc->d_ednssubnet = ednssubnet;
 
           if (!luaconfsLocal->protobufTaggedOnly) {
-            protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, ednssubnet, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags);
+            protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, dc->d_uuid, conn->d_remote, dest, dc->d_ednssubnet.source, true, dh->id, conn->qlen, qname, qtype, qclass, dc->d_policyTags);
           }
         }
         catch(std::exception& e) {
@@ -1533,7 +1552,9 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
     uniqueId = (*t_uuidGenerator)();
   }
 #endif
-  Netmask ednssubnet;
+  EDNSSubnetOpts ednssubnet;
+  bool ecsFound = false;
+  bool ecsParsed = false;
   try {
     DNSName qname;
     uint16_t qtype=0;
@@ -1553,11 +1574,12 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
 
     if(needECS || (t_pdl->get() && (*t_pdl)->d_gettag)) {
       try {
-        getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet);
+        ecsParsed = true;
+        ecsFound = getQNameAndSubnet(question, &qname, &qtype, &qclass, &ednssubnet);
 
         if(t_pdl->get() && (*t_pdl)->d_gettag) {
           try {
-            ctag=(*t_pdl)->gettag(fromaddr, ednssubnet, destaddr, qname, qtype, &policyTags, data);
+            ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags, data);
           }
           catch(std::exception& e)  {
             if(g_logCommonErrors)
@@ -1577,7 +1599,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
 #ifdef HAVE_PROTOBUF
     if(luaconfsLocal->protobufServer) {
       if (!luaconfsLocal->protobufTaggedOnly || !policyTags.empty()) {
-        protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet, false, dh->id, question.size(), qname, qtype, qclass, policyTags);
+        protobufLogQuery(luaconfsLocal->protobufServer, luaconfsLocal->protobufMaskV4, luaconfsLocal->protobufMaskV6, uniqueId, fromaddr, destaddr, ednssubnet.source, false, dh->id, question.size(), qname, qtype, qclass, policyTags);
       }
     }
 #endif /* HAVE_PROTOBUF */
@@ -1589,7 +1611,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
         Netmask requestorNM(fromaddr, fromaddr.sin4.sin_family == AF_INET ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         const ComboAddress& requestor = requestorNM.getMaskedNetwork();
         pbMessage.update(uniqueId, &requestor, &destaddr, false, dh->id);
-        pbMessage.setEDNSSubnet(ednssubnet, ednssubnet.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
+        pbMessage.setEDNSSubnet(ednssubnet.source, ednssubnet.source.isIpv4() ? luaconfsLocal->protobufMaskV4 : luaconfsLocal->protobufMaskV6);
         pbMessage.setQueryTime(g_now.tv_sec, g_now.tv_usec);
         protobufLogResponse(luaconfsLocal->protobufServer, pbMessage);
       }
@@ -1652,11 +1674,13 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
   dc->d_tcp=false;
   dc->d_policyTags = policyTags;
   dc->d_data = data;
+  dc->d_ecsFound = ecsFound;
+  dc->d_ecsParsed = ecsParsed;
+  dc->d_ednssubnet = ednssubnet;
 #ifdef HAVE_PROTOBUF
   if (luaconfsLocal->protobufServer || luaconfsLocal->outgoingProtobufServer) {
     dc->d_uuid = uniqueId;
   }
-  dc->d_ednssubnet = ednssubnet;
 #endif
 
   MT->makeThread(startDoResolve, (void*) dc); // deletes dc
@@ -2761,6 +2785,7 @@ int serviceMain(int argc, char*argv[])
   makeTCPServerSockets();
 
   parseEDNSSubnetWhitelist(::arg()["edns-subnet-whitelist"]);
+  g_useIncomingECS = ::arg().mustDo("use-incoming-edns-subnet");
 
   int forks;
   for(forks = 0; forks < ::arg().asNum("processes") - 1; ++forks) {
@@ -3103,6 +3128,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().setSwitch( "use-incoming-edns-subnet", "Pass along received EDNS Client Subnet information")="";
     ::arg().setSwitch( "pdns-distributes-queries", "If PowerDNS itself should distribute queries over threads")="yes";
     ::arg().setSwitch( "root-nx-trust", "If set, believe that an NXDOMAIN from the root means the TLD does not exist")="yes";
     ::arg().setSwitch( "any-to-tcp","Answer ANY queries with tc=1, shunting to TCP" )="no";
index 431daf4500f88ef510e4194ec28631ae58186cfb..f70e732729a9cf2f73318d94a3ab5ae057a2bbf3 100644 (file)
@@ -3,23 +3,41 @@
 
 NetmaskGroup g_ednssubnets;
 SuffixMatchNode g_ednsdomains;
+bool g_useIncomingECS;
 
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem)
+boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS)
 {
-  static int l_ipv4limit, l_ipv6limit;
+  static uint8_t l_ipv4limit, l_ipv6limit;
   if(!l_ipv4limit) {
     l_ipv4limit = ::arg().asNum("ecs-ipv4-bits");
     l_ipv6limit = ::arg().asNum("ecs-ipv6-bits");
   }
-  if(local.sin4.sin_family != AF_INET || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
-    if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
-      int bits = local.sin4.sin_family == AF_INET ? l_ipv4limit : l_ipv6limit;
-      ComboAddress trunc(local);
-      trunc.truncate(bits);
-      return boost::optional<Netmask>(Netmask(trunc, bits));
+  boost::optional<Netmask> result;
+  ComboAddress trunc;
+  uint8_t bits;
+  if(incomingECS) {
+    if (incomingECS->source.getBits() == 0) {
+      /* RFC7871 says we MUST NOT send any ECS if the source scope is 0 */
+      return result;
     }
+    trunc = incomingECS->source.getMaskedNetwork();
+    bits = incomingECS->source.getBits();
   }
-  return boost::optional<Netmask>();
+  else if(!local.isIPv4() || local.sin4.sin_addr.s_addr) { // detect unset 'requestor'
+    trunc = local;
+    bits = local.isIPv4() ? 32 : 128;
+  }
+  else {
+    /* nothing usable */
+    return result;
+  }
+
+  if(g_ednsdomains.check(dn) || g_ednssubnets.match(rem)) {
+    bits = std::min(bits, (trunc.isIPv4() ? l_ipv4limit : l_ipv6limit));
+    trunc.truncate(bits);
+    return boost::optional<Netmask>(Netmask(trunc, bits));
+  }
+  return result;
 }
 
 void  parseEDNSSubnetWhitelist(const std::string& wlist)
index df1eb46a0e11a4cf81867c43204c049366447216..bdd157d7a6065c5b55492f9f2238eebcfee9394f 100644 (file)
@@ -1116,7 +1116,7 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
              LOG(prefix<<qname<<": query handled by Lua"<<endl);
            }
            else {
-             ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP);
+             ednsmask=getEDNSSubnetMask(d_requestor, qname, *remoteIP, d_incomingECSFound ? d_incomingECS : boost::none);
               if(ednsmask) {
                 LOG(prefix<<qname<<": Adding EDNS Client Subnet Mask "<<ednsmask->toString()<<" to query"<<endl);
               }
index fb58b0e74a4fe3a3757c54d4078b67aed127056a..d7cb11bc21790ef1e67621785ff5e8f165d247d0 100644 (file)
@@ -47,7 +47,7 @@
 #include "mtasker.hh"
 #include "iputils.hh"
 #include "validate.hh"
-
+#include "ednssubnet.hh"
 #include "filterpo.hh"
 
 #include "config.h"
@@ -373,6 +373,7 @@ public:
   static unsigned int s_maxdepth;
   std::unordered_map<std::string,bool> d_discardedPolicies;
   DNSFilterEngine::Policy d_appliedPolicy;
+  boost::optional<const EDNSSubnetOpts&> d_incomingECS;
 #ifdef HAVE_PROTOBUF
   boost::optional<const boost::uuids::uuid&> d_initialRequestId;
 #endif
@@ -389,6 +390,7 @@ public:
   bool d_wasOutOfBand{false};
   bool d_wantsRPZ{true};
   bool d_skipCNAMECheck{false};
+  bool d_incomingECSFound{false};
   
   typedef multi_index_container <
     NegCacheEntry,
@@ -736,7 +738,7 @@ uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree=false);
 uint64_t* pleaseWipePacketCache(const DNSName& canon, bool subtree);
 uint64_t* pleaseWipeAndCountNegCache(const DNSName& canon, bool subtree=false);
 void doCarbonDump(void*);
-boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem);
+boost::optional<Netmask> getEDNSSubnetMask(const ComboAddress& local, const DNSName&dn, const ComboAddress& rem, boost::optional<const EDNSSubnetOpts&> incomingECS);
 void  parseEDNSSubnetWhitelist(const std::string& wlist);
 
 extern __thread struct timeval g_now;
@@ -744,7 +746,6 @@ extern __thread struct timeval g_now;
 extern NetmaskGroup g_ednssubnets;
 extern SuffixMatchNode g_ednsdomains;
 
-
 #ifdef HAVE_PROTOBUF
 extern __thread boost::uuids::random_generator* t_uuidGenerator;
 #endif