]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Add `use-incoming-edns-subnet` to process and pass along ECS 5355/head
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 9 Feb 2017 14:01:41 +0000 (15:01 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Wed, 31 May 2017 14:19:51 +0000 (16:19 +0200)
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.

(cherry picked from commit b40562da39e3be0dcf193163c386eef369dcc4af)

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 f7c2557cb32990464cd66141c83290de335480e2..e1c0e2de697ea430f30ce7ff351e89090d9fd984 100644 (file)
@@ -847,6 +847,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 4a9cbde071ad4d32d4745a39dd15218e2df39195..e0120edfa144edaabf528205cc52b22dec1c6c83 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>
@@ -182,7 +183,7 @@ boost::optional<Netmask>  RecursorLua4::DNSQuestion::getEDNSSubnet()
 
   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 5f8d09673bd2093933c1ce0fe24efe8e9643c95e..b3b0fe205ce05fb6fe07b025ae2573f4464372a0 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};
@@ -700,7 +702,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;
 
@@ -713,7 +726,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 */
@@ -756,6 +769,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;
@@ -781,11 +800,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;
     }
 
@@ -1240,8 +1257,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;
@@ -1259,11 +1277,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)
@@ -1329,7 +1349,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;
@@ -1344,11 +1363,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_tag = (*t_pdl)->gettag(conn->d_remote, dc->d_ednssubnet.source, dest, qname, qtype, &dc->d_policyTags);
             }
             catch(std::exception& e)  {
               if(g_logCommonErrors)
@@ -1370,9 +1390,8 @@ 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;
 
-          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) {
           if(g_logCommonErrors)
@@ -1488,7 +1507,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;
@@ -1508,11 +1529,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);
+            ctag=(*t_pdl)->gettag(fromaddr, ednssubnet.source, destaddr, qname, qtype, &policyTags);
           }
           catch(std::exception& e)  {
             if(g_logCommonErrors)
@@ -1531,7 +1553,7 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
     RecProtoBufMessage pbMessage(DNSProtoBufMessage::DNSProtoBufMessageType::Response);
 #ifdef HAVE_PROTOBUF
     if(luaconfsLocal->protobufServer) {
-      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 */
 
@@ -1542,7 +1564,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);
       }
@@ -1604,11 +1626,13 @@ string* doProcessUDPQuestion(const std::string& question, const ComboAddress& fr
   dc->setLocal(destaddr);
   dc->d_tcp=false;
   dc->d_policyTags = policyTags;
+  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
@@ -2715,6 +2739,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) {
@@ -3055,6 +3080,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")="";
     ::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 7ec195aefa92c91056137d1678371d0f4e082f26..2f44c25b7aef45ce3ef5826154573db9f98b6a4b 100644 (file)
@@ -1131,7 +1131,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 aa043d14cc7d6b2937f3305484a0aa27d4a36355..fdb0867f1ca6edfca86abe07c6c2370aa8cfa250 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,
@@ -735,7 +737,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;
@@ -743,7 +745,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