* 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 <cinttypes>
#include <iostream>
+
+#include "filterpo.hh"
#include "namespaces.hh"
#include "dnsrecords.hh"
{
}
-static bool findNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& 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<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol) const
{
/* for www.powerdns.com, we need to check:
www.powerdns.com.
continue;
}
- if(findNamedPolicy(z->d_propolName, qname, pol)) {
+ if(z->findNSPolicy(qname, pol)) {
// cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
return pol;
}
DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies) const
{
+ Policy pol;
// cout<<"Got question for nameserver IP "<<address.toString()<<endl;
for(const auto& z : d_zones) {
const auto zoneName = z->getName();
continue;
}
- if(auto fnd=z->d_propolNSAddr.lookup(address)) {
+ if(z->findNSIPPolicy(address, pol)) {
// cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
- return fnd->second;;
+ return pol;
}
}
- return Policy();
+ return pol;
}
DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
continue;
}
- if(findNamedPolicy(z->d_qpolName, qname, pol)) {
+ if(z->findQNamePolicy(qname, pol)) {
// cerr<<"Had a hit on the name of the query"<<endl;
return pol;
}
-
- if(auto fnd=z->d_qpolAddr.lookup(ca)) {
+
+ if(z->findClientPolicy(ca, pol)) {
// cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
- return fnd->second;
+ return pol;
}
}
DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& 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<ARecordContent>(r)) {
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)
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<uint32_t>(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<const uint8_t*>(&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);
+ }
+}
#pragma once
#include "iputils.hh"
#include "dns.hh"
+#include "dnsname.hh"
#include "dnsparser.hh"
#include <map>
#include <unordered_map>
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
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.
*/
{
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<DNSRecordContent> d_custom;
std::shared_ptr<std::string> d_name;
{
d_name = std::make_shared<std::string>(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<std::string> getName() const
{
return d_name;
}
+ void dump(FILE * fp) const;
void addClientTrigger(const Netmask& nm, Policy pol);
void addQNameTrigger(const DNSName& nm, Policy pol);
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<DNSName, DNSFilterEngine::Policy>& 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<DNSName, Policy> d_qpolName; // QNAME trigger (RPZ)
NetmaskTree<Policy> d_qpolAddr; // Source address
std::unordered_map<DNSName, Policy> d_propolName; // NSDNAME (RPZ)
NetmaskTree<Policy> d_propolNSAddr; // NSIP (RPZ)
NetmaskTree<Policy> d_postpolAddr; // IP trigger (RPZ)
+ DNSName d_domain;
std::shared_ptr<std::string> d_name;
+ uint32_t d_serial{0};
+ uint32_t d_refresh{0};
};
DNSFilterEngine();
}
return result;
}
+ const std::shared_ptr<Zone> 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<Zone> newZone)
{
d_zones.push_back(newZone);
lci.dfe.addZone(zone);
theL()<<Logger::Warning<<"Done loading RPZ from file '"<<filename<<"'"<<endl;
}
- catch(std::exception& e) {
+ catch(const std::exception& e) {
theL()<<Logger::Error<<"Unable to load RPZ zone from '"<<filename<<"': "<<e.what()<<endl;
}
});
}
}
ComboAddress master(master_, 53);
- if (localAddress != ComboAddress() && localAddress.sin4.sin_family != master.sin4.sin_family)
+ if (localAddress != ComboAddress() && localAddress.sin4.sin_family != master.sin4.sin_family) {
// We were passed a localAddress, check if its AF matches the master's
throw PDNSException("Master address("+master.toString()+") is not of the same Address Family as the local address ("+localAddress.toString()+").");
+ }
+
+ DNSName domain(zoneName);
+ zone->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()<<Logger::Error<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master_<<"': "<<e.what()<<endl;
}
- catch(PDNSException& e) {
+ catch(const PDNSException& e) {
theL()<<Logger::Error<<"Unable to load RPZ zone '"<<zoneName<<"' from '"<<master_<<"': "<<e.reason<<endl;
}
return "done\n";
}
+template<typename T>
+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));
"dump-cache <filename> dump cache contents to the named file\n"
"dump-edns [status] <filename> dump EDNS status to the named file\n"
"dump-nsspeeds <filename> dump nsspeeds statistics to the named file\n"
+"dump-rpz <zone name> <filename> 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"
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") {
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") {
}
}
- 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") {
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`
}
}
L<<Logger::Info<<"Had "<<totremove<<" RPZ removal"<<addS(totremove)<<", "<<totadd<<" addition"<<addS(totadd)<<" for "<<zoneName<<" New serial: "<<oursr->d_st.serial<<endl;
+ newZone->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,
zone->addNSTrigger(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)
else
zone->rmClientTrigger(nm, pol);
- } else if(dr.d_name.isPartOf(rpzIP)) {
+ } else if(dr.d_name.isPartOf(rpzIP)) {
// cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
DNSName filt=dr.d_name.makeRelative(rpzIP);
auto nm=makeNetmaskFromRPZ(filt);
drr.content=".";
DNSRecord dr(drr);
if(dr.d_type == QType::SOA) {
- domain = dr.d_name;
+ domain = dr.d_name;
+ zone->setDomain(domain);
}
else if(dr.d_type == QType::NS) {
continue;
RPZRecordToPolicy(dr, zone, true, defpol, maxTTL);
}
}
- catch(PDNSException& pe) {
+ catch(const PDNSException& pe) {
throw PDNSException("Issue parsing '"+drr.qname.toString()+"' '"+drr.content+"' at "+zpt.getLineOfFile()+": "+pe.reason);
}
}