From: Remi Gacogne Date: Thu, 17 Aug 2017 09:17:56 +0000 (+0200) Subject: rec: Add support for dumping the in-memory RPZ zones to a file X-Git-Tag: auth-4.1.0-rc1~4^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6791663c11d8b352a1b7f54b83d4a87bb955060a;p=thirdparty%2Fpdns.git rec: Add support for dumping the in-memory RPZ zones to a file --- diff --git a/pdns/filterpo.cc b/pdns/filterpo.cc index 8ef02f4db6..a20380c70f 100644 --- a/pdns/filterpo.cc +++ b/pdns/filterpo.cc @@ -19,8 +19,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "filterpo.hh" + +#include #include + +#include "filterpo.hh" #include "namespaces.hh" #include "dnsrecords.hh" @@ -28,7 +31,44 @@ DNSFilterEngine::DNSFilterEngine() { } -static bool findNamedPolicy(const std::unordered_map& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol) +bool DNSFilterEngine::Zone::findQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const +{ + return findNamedPolicy(d_qpolName, qname, pol); +} + +bool DNSFilterEngine::Zone::findNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const +{ + return findNamedPolicy(d_propolName, qname, pol); +} + +bool DNSFilterEngine::Zone::findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const +{ + if (const auto fnd = d_propolNSAddr.lookup(addr)) { + pol = fnd->second; + return true; + } + return false; +} + +bool DNSFilterEngine::Zone::findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const +{ + if (const auto fnd = d_postpolAddr.lookup(addr)) { + pol = fnd->second; + return true; + } + return false; +} + +bool DNSFilterEngine::Zone::findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const +{ + if (const auto fnd = d_qpolAddr.lookup(addr)) { + pol = fnd->second; + return true; + } + return false; +} + +bool DNSFilterEngine::Zone::findNamedPolicy(const std::unordered_map& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol) const { /* for www.powerdns.com, we need to check: www.powerdns.com. @@ -66,7 +106,7 @@ DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qnam continue; } - if(findNamedPolicy(z->d_propolName, qname, pol)) { + if(z->findNSPolicy(qname, pol)) { // cerr<<"Had a hit on the nameserver ("<& discardedPolicies) const { + Policy pol; // cout<<"Got question for nameserver IP "<getName(); @@ -83,12 +124,12 @@ DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress& continue; } - if(auto fnd=z->d_propolNSAddr.lookup(address)) { + if(z->findNSIPPolicy(address, pol)) { // cerr<<"Had a hit on the nameserver ("<second;; + return pol; } } - return Policy(); + return pol; } DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map& discardedPolicies) const @@ -101,14 +142,14 @@ DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, co continue; } - if(findNamedPolicy(z->d_qpolName, qname, pol)) { + if(z->findQNamePolicy(qname, pol)) { // cerr<<"Had a hit on the name of the query"<d_qpolAddr.lookup(ca)) { + + if(z->findClientPolicy(ca, pol)) { // cerr<<"Had a hit on the IP address ("<second; + return pol; } } @@ -117,9 +158,10 @@ DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, co DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector& records, const std::unordered_map& discardedPolicies) const { + Policy pol; ComboAddress ca; for(const auto& r : records) { - if(r.d_place != DNSResourceRecord::ANSWER) + if(r.d_place != DNSResourceRecord::ANSWER) continue; if(r.d_type == QType::A) { if (auto rec = getRR(r)) { @@ -140,11 +182,12 @@ DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector& continue; } - if(auto fnd=z->d_postpolAddr.lookup(ca)) - return fnd->second; + if(z->findResponsePolicy(ca, pol)) { + return pol; + } } } - return Policy(); + return pol; } void DNSFilterEngine::assureZones(size_t zone) @@ -240,3 +283,131 @@ DNSRecord DNSFilterEngine::Policy::getCustomRecord(const DNSName& qname) const return result; } + +std::string DNSFilterEngine::Policy::getKindToString() const +{ + static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru."); + static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"), + rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip."); + static const std::string rpzPrefix("rpz-"); + + switch(d_kind) { + case DNSFilterEngine::PolicyKind::NoAction: + return noaction.toString(); + case DNSFilterEngine::PolicyKind::Drop: + return drop.toString(); + case DNSFilterEngine::PolicyKind::NXDOMAIN: + return g_rootdnsname.toString(); + case PolicyKind::NODATA: + return g_wildcarddnsname.toString(); + case DNSFilterEngine::PolicyKind::Truncate: + return truncate.toString(); + default: + throw std::runtime_error("Unexpected DNSFilterEngine::Policy kind"); + } +} + +DNSRecord DNSFilterEngine::Policy::getRecord(const DNSName& qname) const +{ + DNSRecord dr; + + if (d_kind == PolicyKind::Custom) { + dr = getCustomRecord(qname); + } + else { + dr.d_name = qname; + dr.d_ttl = static_cast(d_ttl); + dr.d_type = QType::CNAME; + dr.d_class = QClass::IN; + dr.d_content = DNSRecordContent::mastermake(QType::CNAME, QClass::IN, getKindToString()); + } + + return dr; +} + +void DNSFilterEngine::Zone::dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol) const +{ + DNSRecord dr = pol.getRecord(name); + fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str()); +} + +DNSName DNSFilterEngine::Zone::maskToRPZ(const Netmask& nm) +{ + int bits = nm.getBits(); + DNSName res(std::to_string(bits)); + const auto addr = nm.getNetwork(); + + if (addr.isIPv4()) { + const uint8_t* bytes = reinterpret_cast(&addr.sin4.sin_addr.s_addr); + res += DNSName(std::to_string(bytes[3]) + "." + std::to_string(bytes[2]) + "." + std::to_string(bytes[1]) + "." + std::to_string(bytes[0])); + } + else { + DNSName temp; + const auto str = addr.toString(); + const auto len = str.size(); + std::string::size_type begin = 0; + + while (begin < len) { + std::string::size_type end = str.find(":", begin); + std::string sub; + if (end != string::npos) { + sub = str.substr(begin, end - begin); + } + else { + sub = str.substr(begin); + } + + if (sub.empty()) { + temp = DNSName("zz") + temp; + } + else { + temp = DNSName(sub) + temp; + } + + if (end == string::npos) { + break; + } + begin = end + 1; + } + res += temp; + } + + return res; +} + + +void DNSFilterEngine::Zone::dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol) const +{ + DNSName full = maskToRPZ(nm); + full += name; + + DNSRecord dr = pol.getRecord(full); + fprintf(fp, "%s %" PRIu32 " IN %s %s\n", dr.d_name.toString().c_str(), dr.d_ttl, QType(dr.d_type).getName().c_str(), dr.d_content->getZoneRepresentation().c_str()); +} + +void DNSFilterEngine::Zone::dump(FILE* fp) const +{ + /* fake the SOA record */ + auto soa = DNSRecordContent::mastermake(QType::SOA, QClass::IN, "fake.RPZ. hostmaster.fake.RPZ. " + std::to_string(d_serial) + " " + std::to_string(d_refresh) + " 600 3600000 604800"); + fprintf(fp, "%s IN SOA %s\n", d_domain.toString().c_str(), soa->getZoneRepresentation().c_str()); + + for (const auto& pair : d_qpolName) { + dumpNamedPolicy(fp, pair.first + d_domain, pair.second); + } + + for (const auto& pair : d_propolName) { + dumpNamedPolicy(fp, pair.first + DNSName("rpz-nsdname.") + d_domain, pair.second); + } + + for (const auto pair : d_qpolAddr) { + dumpAddrPolicy(fp, pair->first, DNSName("rpz-client-ip.") + d_domain, pair->second); + } + + for (const auto pair : d_propolNSAddr) { + dumpAddrPolicy(fp, pair->first, DNSName("rpz-nsip.") + d_domain, pair->second); + } + + for (const auto pair : d_postpolAddr) { + dumpAddrPolicy(fp, pair->first, DNSName("rpz-ip.") + d_domain, pair->second); + } +} diff --git a/pdns/filterpo.hh b/pdns/filterpo.hh index e2ddd91853..3e757914dc 100644 --- a/pdns/filterpo.hh +++ b/pdns/filterpo.hh @@ -22,6 +22,7 @@ #pragma once #include "iputils.hh" #include "dns.hh" +#include "dnsname.hh" #include "dnsparser.hh" #include #include @@ -31,16 +32,16 @@ We know the following actions: - + No action - just pass it on Drop - drop a query, no response - NXDOMAIN - fake up an NXDOMAIN for the query + NXDOMAIN - fake up an NXDOMAIN for the query NODATA - just return no data for this qtype Truncate - set TC bit Modified - "we fake an answer for you" These actions can be caused by the following triggers: - + qname - the query name client-ip - the IP address of the requestor response-ip - an IP address in the response @@ -57,7 +58,7 @@ Wildcard versions (*.domain.com does NOT match domain.com) Netmasks (IPv4 and IPv6) Finally, triggers are grouped in different zones. The "first" zone that has a match - is consulted. Then within that zone, rules again have precedences. + is consulted. Then within that zone, rules again have precedences. */ @@ -74,7 +75,10 @@ public: { return d_kind == rhs.d_kind; // XXX check d_custom too! } + std::string getKindToString() const; DNSRecord getCustomRecord(const DNSName& qname) const; + DNSRecord getRecord(const DNSName& qname) const; + PolicyKind d_kind; std::shared_ptr d_custom; std::shared_ptr d_name; @@ -99,10 +103,23 @@ public: { d_name = std::make_shared(name); } + void setDomain(const DNSName& domain) + { + d_domain = domain; + } + void setSerial(uint32_t serial) + { + d_serial = serial; + } + void setRefresh(uint32_t refresh) + { + d_refresh = refresh; + } const std::shared_ptr getName() const { return d_name; } + void dump(FILE * fp) const; void addClientTrigger(const Netmask& nm, Policy pol); void addQNameTrigger(const DNSName& nm, Policy pol); @@ -116,12 +133,27 @@ public: bool rmNSIPTrigger(const Netmask& nm, Policy pol); bool rmResponseTrigger(const Netmask& nm, Policy pol); + bool findQNamePolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const; + bool findNSPolicy(const DNSName& qname, DNSFilterEngine::Policy& pol) const; + bool findNSIPPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const; + bool findResponsePolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const; + bool findClientPolicy(const ComboAddress& addr, DNSFilterEngine::Policy& pol) const; + + private: + static DNSName maskToRPZ(const Netmask& nm); + bool findNamedPolicy(const std::unordered_map& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol) const; + void dumpNamedPolicy(FILE* fp, const DNSName& name, const Policy& pol) const; + void dumpAddrPolicy(FILE* fp, const Netmask& nm, const DNSName& name, const Policy& pol) const; + std::unordered_map d_qpolName; // QNAME trigger (RPZ) NetmaskTree d_qpolAddr; // Source address std::unordered_map d_propolName; // NSDNAME (RPZ) NetmaskTree d_propolNSAddr; // NSIP (RPZ) NetmaskTree d_postpolAddr; // IP trigger (RPZ) + DNSName d_domain; std::shared_ptr d_name; + uint32_t d_serial{0}; + uint32_t d_refresh{0}; }; DNSFilterEngine(); @@ -139,6 +171,16 @@ public: } return result; } + const std::shared_ptr getZone(const std::string& name) const + { + for (const auto zone : d_zones) { + const auto& zName = zone->getName(); + if (zName && *zName == name) { + return zone; + } + } + return nullptr; + } size_t addZone(std::shared_ptr newZone) { d_zones.push_back(newZone); diff --git a/pdns/rec-lua-conf.cc b/pdns/rec-lua-conf.cc index 9e0c8a469b..da2d7491e7 100644 --- a/pdns/rec-lua-conf.cc +++ b/pdns/rec-lua-conf.cc @@ -128,7 +128,7 @@ void loadRecursorLuaConfig(const std::string& fname, bool checkOnly) lci.dfe.addZone(zone); theL()<setDomain(domain); zone->setName(polName); + zone->setRefresh(refresh); size_t zoneIdx = lci.dfe.addZone(zone); if (!checkOnly) { - auto sr=loadRPZFromServer(master, DNSName(zoneName), zone, defpol, maxTTL, tt, maxReceivedXFRMBytes * 1024 * 1024, localAddress); + auto sr=loadRPZFromServer(master, domain, zone, defpol, maxTTL, tt, maxReceivedXFRMBytes * 1024 * 1024, localAddress); if(refresh) sr->d_st.refresh=refresh; + zone->setSerial(sr->d_st.serial); std::thread t(RPZIXFRTracker, master, DNSName(zoneName), defpol, maxTTL, zoneIdx, tt, sr, maxReceivedXFRMBytes * 1024 * 1024, localAddress); t.detach(); } } - catch(std::exception& e) { + catch(const std::exception& e) { theL()< +string doDumpRPZ(T begin, T end) +{ + T i=begin; + + if (i == end) { + return "No zone name specified\n"; + } + string zoneName = *i; + i++; + + if (i == end) { + return "No file name specified\n"; + } + string fname = *i; + + auto luaconf = g_luaconfs.getLocal(); + const auto zone = luaconf->dfe.getZone(zoneName); + if (!zone) { + return "No RPZ zone named "+zoneName+"\n"; + } + + int fd = open(fname.c_str(), O_CREAT | O_EXCL | O_WRONLY, 0660); + + if(fd < 0) { + return "Error opening dump file for writing: "+string(strerror(errno))+"\n"; + } + + FILE* fp = fdopen(fd, "w"); + if (!fp) { + close(fd); + return "Error converting file descriptor: "+string(strerror(errno))+"\n"; + } + + zone->dump(fp); + fclose(fp); + + return "done\n"; +} + uint64_t* pleaseWipeCache(const DNSName& canon, bool subtree) { return new uint64_t(t_RC->doWipeCache(canon, subtree)); @@ -1150,6 +1190,7 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP "dump-cache dump cache contents to the named file\n" "dump-edns [status] dump EDNS status to the named file\n" "dump-nsspeeds dump nsspeeds statistics to the named file\n" +"dump-rpz dump the content of a RPZ zone to the named file\n" "get [key1] [key2] .. get specific statistics\n" "get-all get all statistics\n" "get-ntas get all configured Negative Trust Anchors\n" @@ -1183,10 +1224,10 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP if(cmd=="get-all") return getAllStats(); - if(cmd=="get") + if(cmd=="get") return doGet(begin, end); - - if(cmd=="get-parameter") + + if(cmd=="get-parameter") return doGetParameter(begin, end); if(cmd=="quit") { @@ -1197,25 +1238,29 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP if(cmd=="version") { return getPDNSVersion()+"\n"; } - + if(cmd=="quit-nicely") { *command=&doExitNicely; return "bye nicely\n"; - } + } - if(cmd=="dump-cache") + if(cmd=="dump-cache") return doDumpCache(begin, end); - if(cmd=="dump-ednsstatus" || cmd=="dump-edns") + if(cmd=="dump-ednsstatus" || cmd=="dump-edns") return doDumpEDNSStatus(begin, end); if(cmd=="dump-nsspeeds") return doDumpNSSpeeds(begin, end); - if(cmd=="wipe-cache" || cmd=="flushname") + if(cmd=="dump-rpz") { + return doDumpRPZ(begin, end); + } + + if(cmd=="wipe-cache" || cmd=="flushname") return doWipeCache(begin, end); - if(cmd=="reload-lua-script") + if(cmd=="reload-lua-script") return doQueueReloadLuaScript(begin, end); if(cmd=="reload-lua-config") { @@ -1235,10 +1280,10 @@ string RecursorControlParser::getAnswer(const string& question, RecursorControlP } } - if(cmd=="set-carbon-server") + if(cmd=="set-carbon-server") return doSetCarbonServer(begin, end); - if(cmd=="trace-regex") + if(cmd=="trace-regex") return doTraceRegex(begin, end); if(cmd=="unload-lua-script") { diff --git a/pdns/recursordist/docs/manpages/rec_control.rst b/pdns/recursordist/docs/manpages/rec_control.rst index c0e5303875..352b5d577c 100644 --- a/pdns/recursordist/docs/manpages/rec_control.rst +++ b/pdns/recursordist/docs/manpages/rec_control.rst @@ -85,6 +85,12 @@ dump-nsspeeds *FILENAME* dumping, the recursor will not answer questions. Statistics are kept per thread, and the dumps end up in the same file. +dump-rpz *ZONE NAME* *FILE NAME* + Dumps the content of the RPZ zone named *ZONE NAME* to the *FILENAME* + mentioned. This file should not exist already, PowerDNS will refuse to + overwrite it otherwise. While dumping, the recursor will not answer + questions. + get *STATISTIC* [*STATISTIC*]... Retrieve a statistic. For items that can be queried, see :doc:`../metrics` diff --git a/pdns/reczones.cc b/pdns/reczones.cc index 53c90edea1..c1d46543c4 100644 --- a/pdns/reczones.cc +++ b/pdns/reczones.cc @@ -390,6 +390,7 @@ void RPZIXFRTracker(const ComboAddress& master, const DNSName& zoneName, boost:: } } L<d_st.serial<setSerial(oursr->d_st.serial); /* we need to replace the existing zone with the new one, but we don't want to touch anything else, especially other zones, diff --git a/pdns/rpzloader.cc b/pdns/rpzloader.cc index bddf4dd63e..b5977d9d04 100644 --- a/pdns/rpzloader.cc +++ b/pdns/rpzloader.cc @@ -143,7 +143,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptraddNSTrigger(filt, pol); else zone->rmNSTrigger(filt, pol); - } else if(dr.d_name.isPartOf(rpzClientIP)) { + } else if(dr.d_name.isPartOf(rpzClientIP)) { DNSName filt=dr.d_name.makeRelative(rpzClientIP); auto nm=makeNetmaskFromRPZ(filt); if(addOrRemove) @@ -151,7 +151,7 @@ void RPZRecordToPolicy(const DNSRecord& dr, std::shared_ptrrmClientTrigger(nm, pol); - } else if(dr.d_name.isPartOf(rpzIP)) { + } else if(dr.d_name.isPartOf(rpzIP)) { // cerr<<"Should apply answer content IP policy: "<setDomain(domain); } else if(dr.d_type == QType::NS) { continue; @@ -236,7 +237,7 @@ void loadRPZFromFile(const std::string& fname, std::shared_ptr