]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
rec: Add a new policy filter event Lua hook
authorRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Aug 2020 13:52:00 +0000 (15:52 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Mon, 24 Aug 2020 15:29:41 +0000 (17:29 +0200)
We now do the filtering during the resolution, instead of at the end,
to better match the RPZ specifications. Unfortunately it means that
we need a new hook to be able to act on policy events, since they
can occur in various places.

pdns/lua-recursor4.cc
pdns/lua-recursor4.hh
pdns/pdns_recursor.cc
pdns/recursordist/test-syncres_cc.cc
pdns/syncres.cc
regression-tests.recursor/config.sh
regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result

index 79cc6d48fc56dfdaba1612d6a890250656f98de7..5f46e23bbe4f2664610177e138eb6fb76f6d7d90 100644 (file)
@@ -374,6 +374,38 @@ void RecursorLua4::postPrepareContext()
   d_lw->writeFunction("getregisteredname", [](const DNSName &dname) {
       return getRegisteredName(dname);
   });
+
+  d_lw->registerMember<const DNSName (PolicyEvent::*)>("qname", [](const PolicyEvent& event) -> const DNSName& { return event.qname; }, [](PolicyEvent& event, const DNSName& newName) { (void) newName; });
+  d_lw->registerMember<uint16_t (PolicyEvent::*)>("qtype", [](const PolicyEvent& event) -> uint16_t { return event.qtype.getCode(); }, [](PolicyEvent& event, uint16_t newType) { (void) newType; });
+  d_lw->registerMember<bool (PolicyEvent::*)>("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& event, bool newTcp) { (void) newTcp; });
+  d_lw->registerMember<const ComboAddress (PolicyEvent::*)>("remote", [](const PolicyEvent& event) -> const ComboAddress& { return event.remote; }, [](PolicyEvent& event, const ComboAddress& newRemote) { (void) newRemote; });
+  d_lw->registerMember("appliedPolicy", &PolicyEvent::appliedPolicy);
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::vector<std::pair<int, std::string> >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector<std::pair<int, std::string> >& tags) {
+      if (event.policyTags) {
+        event.policyTags->clear();
+        event.policyTags->reserve(tags.size());
+        for (const auto& tag : tags) {
+          event.policyTags->insert(tag.second);
+        }
+      }
+    });
+  d_lw->registerFunction<std::vector<std::pair<int, std::string> >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) {
+      std::vector<std::pair<int, std::string> > ret;
+      if (event.policyTags) {
+        int count = 1;
+        ret.reserve(event.policyTags->size());
+        for (const auto& tag : *event.policyTags) {
+          ret.push_back({count++, tag});
+        }
+      }
+      return ret;
+    });
+  d_lw->registerFunction<void(PolicyEvent::*)(const std::string&)>("discardPolicy", [](PolicyEvent& event, const std::string& policy) {
+    if (event.discardedPolicies) {
+      (*event.discardedPolicies)[policy] = true;
+    }
+  });
 }
 
 void RecursorLua4::postLoad() {
@@ -388,6 +420,8 @@ void RecursorLua4::postLoad() {
   d_ipfilter = d_lw->readVariable<boost::optional<ipfilter_t>>("ipfilter").get_value_or(0);
   d_gettag = d_lw->readVariable<boost::optional<gettag_t>>("gettag").get_value_or(0);
   d_gettag_ffi = d_lw->readVariable<boost::optional<gettag_ffi_t>>("gettag_ffi").get_value_or(0);
+
+  d_policyHitEventFilter = d_lw->readVariable<boost::optional<policyEventFilter_t>>("policyEventFilter").get_value_or(0);
 }
 
 void RecursorLua4::getFeatures(Features & features) {
@@ -448,6 +482,25 @@ bool RecursorLua4::ipfilter(const ComboAddress& remote, const ComboAddress& loca
   return false; // don't block
 }
 
+bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string,bool>& dicardedPolicies) const
+{
+  if (!d_policyHitEventFilter) {
+    return false;
+  }
+
+  PolicyEvent event(remote, qname, qtype, tcp);
+  event.appliedPolicy = &policy;
+  event.policyTags = &tags;
+  event.discardedPolicies = &dicardedPolicies;
+
+  if (d_policyHitEventFilter(event)) {
+    return true;
+  }
+  else {
+    return false;
+  }
+}
+
 unsigned int RecursorLua4::gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const
 {
   if(d_gettag) {
index 08bfefe0f6246127bce92e4e4dc473821f01b71b..58983492ab60e470e2fda9f672da226bf1faf4f2 100644 (file)
@@ -115,6 +115,20 @@ public:
     DNSName followupName;
   };
 
+  struct PolicyEvent
+  {
+    PolicyEvent(const ComboAddress& rem, const DNSName& name, const QType& type, bool tcp): qname(name), qtype(type), remote(rem), isTcp(tcp)
+    {
+    }
+    const DNSName& qname;
+    const QType qtype;
+    const ComboAddress& remote;
+    const bool isTcp;
+    DNSFilterEngine::Policy* appliedPolicy{nullptr};
+    std::unordered_set<std::string>* policyTags{nullptr};
+    std::unordered_map<std::string,bool>* discardedPolicies{nullptr};
+  };
+
   unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector<ProxyProtocolValue>& proxyProtocolValues) const;
   unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set<std::string>* policyTags, std::vector<DNSRecord>& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector<ProxyProtocolValue>& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional<int>& rcode, uint32_t& ttlCap, bool& variable, bool& logQuery, bool& logResponse, bool& followCNAMERecords) const;
 
@@ -128,6 +142,8 @@ public:
   bool preoutquery(const ComboAddress& ns, const ComboAddress& requestor, const DNSName& query, const QType& qtype, bool isTcp, vector<DNSRecord>& res, int& ret) const;
   bool ipfilter(const ComboAddress& remote, const ComboAddress& local, const struct dnsheader&) const;
 
+  bool policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string,bool>& dicardedPolicies) const;
+
   bool needDQ() const
   {
     return (d_prerpz ||
@@ -154,5 +170,7 @@ private:
   bool genhook(const luacall_t& func, DNSQuestion& dq, int& ret) const;
   typedef std::function<bool(ComboAddress,ComboAddress, struct dnsheader)> ipfilter_t;
   ipfilter_t d_ipfilter;
+  typedef std::function<bool(PolicyEvent&)> policyEventFilter_t;
+  policyEventFilter_t d_policyHitEventFilter;
 };
 
index eb3cd54ecc4a7a229ed488bbaf58441497cde9a5..103b80298f3c788c2cec2e9f79ab2f1bb04eefdf 100644 (file)
@@ -132,7 +132,6 @@ static thread_local uint64_t t_frameStreamServersGeneration;
 thread_local std::unique_ptr<MT_t> MT; // the big MTasker
 std::unique_ptr<MemRecursorCache> s_RC;
 
-
 thread_local std::unique_ptr<RecursorPacketCache> t_packetCache;
 thread_local FDMultiplexer* t_fdm{nullptr};
 thread_local std::unique_ptr<addrringbuf_t> t_remotes, t_servfailremotes, t_largeanswerremotes, t_bogusremotes;
@@ -1459,7 +1458,7 @@ static void startDoResolve(void *p)
       if (luaconfsLocal->dfe.getClientPolicy(dc->d_source, sr.d_discardedPolicies, appliedPolicy)) {
         mergePolicyTags(dc->d_policyTags, appliedPolicy.getTags());
       }
-     }
+    }
 
     /* If we already have an answer generated from gettag_ffi, let's see if the filtering policies
        should be applied to it */
@@ -1495,7 +1494,7 @@ static void startDoResolve(void *p)
         goto haveAnswer;
       }
     }
-    
+
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
     if (!t_pdl || !t_pdl->preresolve(dq, res)) {
 
@@ -1505,17 +1504,25 @@ static void startDoResolve(void *p)
       }
 
       sr.setWantsRPZ(wantsRPZ);
+
       if (wantsRPZ && appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
-        auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, false);
-        if (policyResult == PolicyResult::HaveAnswer) {
-          goto haveAnswer;
+
+        if (t_pdl && t_pdl->policyHitEventFilter(dc->d_remote, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_tcp, appliedPolicy, dc->d_policyTags, sr.d_discardedPolicies)) {
+          /* reset to no match */
+          appliedPolicy = DNSFilterEngine::Policy();
         }
-        else if (policyResult == PolicyResult::Drop) {
-          return;
+        else {
+          auto policyResult = handlePolicyHit(appliedPolicy, dc, sr, res, ret, pw, false);
+          if (policyResult == PolicyResult::HaveAnswer) {
+            goto haveAnswer;
+          }
+          else if (policyResult == PolicyResult::Drop) {
+            return;
+          }
         }
       }
 
-      // Query got not handled for QNAME Policy reasons, now actually go out to find an answer
+      // Query did not get handled for Client IP or QNAME Policy reasons, now actually go out to find an answer
       try {
         sr.d_appliedPolicy = appliedPolicy;
         sr.d_policyTags = std::move(dc->d_policyTags);
@@ -1529,7 +1536,7 @@ static void startDoResolve(void *p)
         shouldNotValidate = sr.wasOutOfBand();
       }
       catch (const ImmediateQueryDropException& e) {
-#warning We need to export a protobuf (and NOD lookup?) message if requested!
+        // XXX We need to export a protobuf message (and do a NOD lookup) if requested!
         g_stats.policyDrops++;
         g_log<<Logger::Debug<<"Dropping query because of a filtering policy "<<makeLoginfo(dc)<<endl;
         return;
index af352b31eb18c9e0134c7ee2e58dc9def4368fd2..2b99057d6246d7f6d4111a416a320fba2f0a1dd5 100644 (file)
@@ -31,6 +31,11 @@ bool RecursorLua4::preoutquery(const ComboAddress& ns, const ComboAddress& reque
   return false;
 }
 
+bool RecursorLua4::policyHitEventFilter(const ComboAddress& remote, const DNSName& qname, const QType& qtype, bool tcp, DNSFilterEngine::Policy& policy, std::unordered_set<std::string>& tags, std::unordered_map<std::string,bool>& dicardedPolicies) const
+{
+  return false;
+}
+
 int asyncresolve(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, const std::shared_ptr<std::vector<std::unique_ptr<RemoteLogger>>>& outgoingLoggers, const std::shared_ptr<std::vector<std::unique_ptr<FrameStreamLogger>>>& fstrmLoggers, const std::set<uint16_t>& exportTypes, LWResult* res, bool* chained)
 {
   return 0;
index 6254c4b718f7521083007bedb758f82cbcadf583..962e1e35ad8959fef29836d7718dd49d1584b1db 100644 (file)
@@ -1965,12 +1965,17 @@ static bool rpzHitShouldReplaceContent(const DNSName& qname, const QType& qtype,
 
 void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, const QType& qtype, std::vector<DNSRecord>& ret, bool& done, int& rcode, unsigned int depth)
 {
+  if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+    /* reset to no match */
+    d_appliedPolicy = DNSFilterEngine::Policy();
+    return;
+  }
+
   /* don't account truncate actions for TCP queries, since they are not applied */
   if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::Truncate || !d_queryReceivedOverTCP) {
     ++g_stats.policyResults[d_appliedPolicy.d_kind];
   }
 
-  cerr<<"Handling policy hit for "<<qname<<" of type "<<(int)d_appliedPolicy.d_kind<<endl;
   switch (d_appliedPolicy.d_kind) {
 
   case DNSFilterEngine::PolicyKind::NoAction:
@@ -2002,9 +2007,7 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
 
   case DNSFilterEngine::PolicyKind::Custom:
     {
-      cerr<<"custom"<<endl;
       if (rpzHitShouldReplaceContent(qname, qtype, ret)) {
-        cerr<<"clearing"<<endl;
         ret.clear();
       }
 
@@ -2016,8 +2019,6 @@ void SyncRes::handlePolicyHit(const std::string& prefix, const DNSName& qname, c
 
         if (dr.d_name == qname && dr.d_type == QType::CNAME && qtype != QType::CNAME) {
           if (auto content = getRR<CNAMERecordContent>(dr)) {
-#warning FIXME shall we stop RPZ processing from here?
-            // https://tools.ietf.org/html/draft-vixie-dnsop-dns-rpz-00#section-6
             vState newTargetState = vState::Indeterminate;
             handleNewTarget(prefix, qname, content->getTarget(), qtype.getCode(), ret, rcode, depth, {}, newTargetState);
           }
@@ -3681,8 +3682,14 @@ bool SyncRes::processAnswer(unsigned int depth, LWResult& lwr, const DNSName& qn
         if (match) {
           mergePolicyTags(d_policyTags, d_appliedPolicy.getTags());
           if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
-            LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
-            throw PolicyHitException();
+            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+              /* reset to no match */
+              d_appliedPolicy = DNSFilterEngine::Policy();
+            }
+            else {
+              LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<d_appliedPolicy.getName()<<"'"<<endl);
+              throw PolicyHitException();
+            }
           }
         }
       }
@@ -3717,7 +3724,13 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
 
   if (nameserversBlockedByRPZ(luaconfsLocal->dfe, nameservers)) {
     /* RPZ hit */
-    throw PolicyHitException();
+    if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+      /* reset to no match */
+      d_appliedPolicy = DNSFilterEngine::Policy();
+    }
+    else {
+      throw PolicyHitException();
+    }
   }
 
   LOG(endl);
@@ -3817,7 +3830,13 @@ int SyncRes::doResolveAt(NsSet &nameservers, DNSName auth, bool flawedNSSet, con
           LOG(endl);
           if (hitPolicy) { //implies d_wantsRPZ
             /* RPZ hit */
-            throw PolicyHitException();
+            if (d_pdl && d_pdl->policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) {
+              /* reset to no match */
+              d_appliedPolicy = DNSFilterEngine::Policy();
+            }
+            else {
+              throw PolicyHitException();
+            }
           }
         }
 
index 5e2b46dd1e40d78d506ace0684df126065f19348..7ef7faf13ed15eeb1cff8b40517791908bf971fe 100755 (executable)
@@ -698,9 +698,14 @@ function preresolve(dq)
   if dq.qname:equal("android.marvin.example.net") then
     dq.wantsRPZ = false -- disable RPZ
   end
-  if dq.appliedPolicy.policyKind == pdns.policykinds.Custom then
-    if dq.qname:equal("www3.example.net") then
-      dq.appliedPolicy.policyCustom = "www2.example.net"
+  return false
+end
+
+function policyEventFilter(event)
+  if event.appliedPolicy.policyKind == pdns.policykinds.Custom then
+    if event.qname:equal("www3.example.net") then
+      event.appliedPolicy.policyCustom = "www2.example.net"
+      return false
     end
   end
   return false
index 9a48ef615732f766746fde0ebe3a8041133993e4..490a8a27af3cab51db574146048c498960e1f626 100644 (file)
@@ -1,4 +1,4 @@
-0      www.trillian.example.net.       IN      CNAME   3600    www2.arthur.example.net.
-0      www2.arthur.example.net.        IN      A       3600    192.0.2.6
+0      www.trillian.example.net.       IN      CNAME   3600    www3.arthur.example.net.
+0      www3.arthur.example.net.        IN      A       3600    192.0.2.6
 Rcode: 0 (No Error), RD: 1, QR: 1, TC: 0, AA: 0, opcode: 0
 Reply to question for qname='www.trillian.example.net.', qtype=A