From: Remi Gacogne Date: Mon, 24 Aug 2020 13:52:00 +0000 (+0200) Subject: rec: Add a new policy filter event Lua hook X-Git-Tag: rec-4.4.0-beta1~1^2~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=acf86ed73e07fb14ca813966ebecac7f6f8d5261;p=thirdparty%2Fpdns.git rec: Add a new policy filter event Lua hook 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. --- diff --git a/pdns/lua-recursor4.cc b/pdns/lua-recursor4.cc index 79cc6d48fc..5f46e23bbe 100644 --- a/pdns/lua-recursor4.cc +++ b/pdns/lua-recursor4.cc @@ -374,6 +374,38 @@ void RecursorLua4::postPrepareContext() d_lw->writeFunction("getregisteredname", [](const DNSName &dname) { return getRegisteredName(dname); }); + + d_lw->registerMember("qname", [](const PolicyEvent& event) -> const DNSName& { return event.qname; }, [](PolicyEvent& event, const DNSName& newName) { (void) newName; }); + d_lw->registerMember("qtype", [](const PolicyEvent& event) -> uint16_t { return event.qtype.getCode(); }, [](PolicyEvent& event, uint16_t newType) { (void) newType; }); + d_lw->registerMember("isTcp", [](const PolicyEvent& event) -> bool { return event.isTcp; }, [](PolicyEvent& event, bool newTcp) { (void) newTcp; }); + d_lw->registerMember("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("addPolicyTag", [](PolicyEvent& event, const std::string& tag) { if (event.policyTags) { event.policyTags->insert(tag); } }); + d_lw->registerFunction >&)>("setPolicyTags", [](PolicyEvent& event, const std::vector >& 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 >(PolicyEvent::*)()>("getPolicyTags", [](const PolicyEvent& event) { + std::vector > 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("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>("ipfilter").get_value_or(0); d_gettag = d_lw->readVariable>("gettag").get_value_or(0); d_gettag_ffi = d_lw->readVariable>("gettag_ffi").get_value_or(0); + + d_policyHitEventFilter = d_lw->readVariable>("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& tags, std::unordered_map& 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* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector& proxyProtocolValues) const { if(d_gettag) { diff --git a/pdns/lua-recursor4.hh b/pdns/lua-recursor4.hh index 08bfefe0f6..58983492ab 100644 --- a/pdns/lua-recursor4.hh +++ b/pdns/lua-recursor4.hh @@ -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* policyTags{nullptr}; + std::unordered_map* discardedPolicies{nullptr}; + }; + unsigned int gettag(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, LuaContext::LuaObject& data, const EDNSOptionViewMap&, bool tcp, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, const std::vector& proxyProtocolValues) const; unsigned int gettag_ffi(const ComboAddress& remote, const Netmask& ednssubnet, const ComboAddress& local, const DNSName& qname, uint16_t qtype, std::unordered_set* policyTags, std::vector& records, LuaContext::LuaObject& data, const EDNSOptionViewMap& ednsOptions, bool tcp, const std::vector& proxyProtocolValues, std::string& requestorId, std::string& deviceId, std::string& deviceName, std::string& routingTag, boost::optional& 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& 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& tags, std::unordered_map& 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 ipfilter_t; ipfilter_t d_ipfilter; + typedef std::function policyEventFilter_t; + policyEventFilter_t d_policyHitEventFilter; }; diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index eb3cd54ecc..103b80298f 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -132,7 +132,6 @@ static thread_local uint64_t t_frameStreamServersGeneration; thread_local std::unique_ptr MT; // the big MTasker std::unique_ptr s_RC; - thread_local std::unique_ptr t_packetCache; thread_local FDMultiplexer* t_fdm{nullptr}; thread_local std::unique_ptr 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<& tags, std::unordered_map& 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& srcmask, boost::optional context, const std::shared_ptr>>& outgoingLoggers, const std::shared_ptr>>& fstrmLoggers, const std::set& exportTypes, LWResult* res, bool* chained) { return 0; diff --git a/pdns/syncres.cc b/pdns/syncres.cc index 6254c4b718..962e1e35ad 100644 --- a/pdns/syncres.cc +++ b/pdns/syncres.cc @@ -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& 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 "<(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 "<policyHitEventFilter(d_requestor, qname, qtype, d_queryReceivedOverTCP, d_appliedPolicy, d_policyTags, d_discardedPolicies)) { + /* reset to no match */ + d_appliedPolicy = DNSFilterEngine::Policy(); + } + else { + LOG("however "<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(); + } } } diff --git a/regression-tests.recursor/config.sh b/regression-tests.recursor/config.sh index 5e2b46dd1e..7ef7faf13e 100755 --- a/regression-tests.recursor/config.sh +++ b/regression-tests.recursor/config.sh @@ -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 diff --git a/regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result b/regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result index 9a48ef6157..490a8a27af 100644 --- a/regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result +++ b/regression-tests.recursor/cross-zone-cname-bogus-nxdomain/expected_result @@ -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