TDS_Version=7.1
__EOF__
- run:
- command: cat /usr/share/tdsodbc/odbcinst.ini >> /etc/odbcinst.ini
+ command: cat /usr/share/tdsodbc/odbcinst.ini <(echo Threading=1) >> /etc/odbcinst.ini
- run:
name: create database
command: echo 'create database pdns' | isql -v pdns-mssql-docker-nodb sa SAsa12%%
# run "./timestamp ./start-test-stop 5300 gmysql-nsec3-optout-both"
run "./timestamp ./start-test-stop 5300 gmysql-nsec3-narrow"
+ run "sudo perl -i -pe 's/\]/]\nThreading=1/g' /etc/odbcinst.ini"
+ run "cat /etc/odbcinst.ini"
run "export GODBC_SQLITE3_DSN=pdns-sqlite3-1"
run "./timestamp ./start-test-stop 5300 godbc_sqlite3-nsec3"
Show the content of the cache.
-current-config
-^^^^^^^^^^^^^^
+current-config [diff]
+^^^^^^^^^^^^^^^^^^^^^
-Show the currently running configuration. The output has the same
-format as ``pdns_server --config``. You'll notice that all the
-configuration values are uncommented. This is because PowerDNS
-simply has values, and the default isn't known at runtime.
+Show the currently running configuration. The output has the same format as ``pdns_server --config``. With the diff option only modified options are included in the output.
cycle
^^^^^
--control-console Run the server in a special monitor mode. This enables detailed
logging and exposes the raw control socket.
--loglevel=<LEVEL> Set the logging level.
+--config Show the currently configuration. There are three optional values:
+ --config=default show the default configuration.
+ --config=diff show modified options in the curent configuration.
+ --config=check parse the current configuration, with error checking.
--help To view more options that are available use this program.
See also
}
void setup(const std::string& method_, const std::string& url_) {
this->url.parse(url_);
- this->headers["host"] = this->url.host;
+ this->headers["host"] = this->url.host.find(":") == std::string::npos ? this->url.host : "[" + this->url.host + "]";
this->method = method_;
std::transform(this->method.begin(), this->method.end(), this->method.begin(), ::toupper);
this->headers["user-agent"] = "YaHTTP v1.0";
host = url.substr(pos, pos1-pos);
pos = pos1;
}
- if ( (pos1 = host.find_first_of(":")) != std::string::npos ) {
+ if (host.at(0) == '[') { // IPv6
+ if ((pos1 = host.find_first_of("]")) == std::string::npos) {
+ // incomplete address
+ return false;
+ }
+ size_t pos2;
+ if ((pos2 = host.find_first_of(":", pos1)) != std::string::npos) {
+ std::istringstream tmp(host.substr(pos2 + 1));
+ tmp >> port;
+ }
+ host = host.substr(1, pos1 - 1);
+ } else if ( (pos1 = host.find_first_of(":")) != std::string::npos ) {
std::istringstream tmp(host.substr(pos1+1));
tmp >> port;
host = host.substr(0, pos1);
endif
pdns.conf-dist: pdns_server
- $(AM_V_GEN)./pdns_server --no-config --config 2>/dev/null > $@
+ $(AM_V_GEN)./pdns_server --no-config --config=default 2>/dev/null > $@
testrunner_SOURCES = \
arguments.cc \
return params[var];
}
+void ArgvMap::setDefault(const string &var, const string &value)
+{
+ if(! defaultmap.count(var))
+ defaultmap.insert(pair<string, string>(var, value));
+}
+
+void ArgvMap::setDefaults()
+{
+ for(map<string,string>::const_iterator i=params.begin();i!=params.end();++i)
+ if(! defaultmap.count(i->first))
+ defaultmap.insert(*i);
+}
+
bool ArgvMap::mustDo(const string &var)
{
return ((*this)[var]!="no") && ((*this)[var]!="off");
return help;
}
-string ArgvMap::configstring(bool current)
+string ArgvMap::configstring(bool running, bool full)
{
string help;
- if (current)
- help="# Autogenerated configuration file based on running instance\n";
+ if (running)
+ help="# Autogenerated configuration file based on running instance ("+nowTime()+")\n\n";
else
- help="# Autogenerated configuration file template\n";
-
+ help="# Autogenerated configuration file template\n\n";
+
for(const auto& i: helpmap) {
if(d_typeMap[i.first]=="Command")
continue;
- help+="#################################\n";
- help+="# ";
- help+=i.first;
- help+="\t";
- help+=i.second;
- help+="\n#\n";
- if (current) {
- help+=i.first+"="+params[i.first]+"\n\n";
+ if (! defaultmap.count(i.first)) {
+ throw ArgException(string("Default for parameter '")+i.first+"' not set");
+ }
+
+ if (!running || full) {
+ help+="#################################\n";
+ help+="# ";
+ help+=i.first;
+ help+="\t";
+ help+=i.second;
+ help+="\n#\n";
+ } else {
+ if (defaultmap[i.first] == params[i.first]) {
+ continue;
+ }
+ }
+
+ if (! running || defaultmap[i.first] == params[i.first]) {
+ help+="# ";
+ }
+
+ if (running) {
+ help+=i.first+"="+params[i.first]+"\n";
+ if (full) {
+ help+="\n";
+ }
} else {
- help+="# "+i.first+"="+params[i.first]+"\n\n";
+ help+=i.first+"="+defaultmap[i.first]+"\n\n";
}
}
return help;
void setCmd(const string &, const string &); //!< Add a command flag
string &setSwitch(const string &, const string &); //!< Add a switch flag
string helpstring(string prefix=""); //!< generates the --help
- string configstring(bool current=false); //!< generates the --mkconfig
+ string configstring(bool current, bool full); //!< generates the --config
bool contains(const string &var, const string &val);
bool isEmpty(const string &var); //!< checks if variable has value
+ void setDefault(const string &var, const string &value);
+ void setDefaults();
vector<string>list();
string getHelp(const string &item);
typedef map<string,string> params_t;
params_t params;
map<string,string> helpmap;
+ map<string,string> defaultmap;
map<string,string> d_typeMap;
vector<string> d_cmds;
std::set<string> d_cleared;
::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
::arg().set("rng", "Specify the random number generator to use. Valid values are auto,sodium,openssl,getrandom,arc4random,urandom.")="auto";
+ ::arg().setDefaults();
}
static time_t s_start=time(0);
{
string fullname=d_name+suffix+"-"+param;
arg().set(fullname,help)=value;
+ arg().setDefault(fullname,value);
}
const string &BackendFactory::getName() const
string DLCurrentConfigHandler(const vector<string>&parts, Utility::pid_t ppid)
{
- return ::arg().configstring(true);
+ if(parts.size() > 1) {
+ if(parts.size() == 2 && parts[1] == "diff") {
+ return ::arg().configstring(true, false);
+ }
+ return "Syntax: current-config [diff]";
+ }
+ return ::arg().configstring(true, true);
}
string DLRQuitHandler(const vector<string>&parts, Utility::pid_t ppid)
return false;
}
-DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies) const
+bool DNSFilterEngine::getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
{
// cout<<"Got question for nameserver name "<<qname<<endl;
std::vector<bool> zoneEnabled(d_zones.size());
for (const auto& z : d_zones) {
bool enabled = true;
const auto zoneName = z->getName();
- if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+ if (z->getPriority() >= pol.d_priority) {
+ enabled = false;
+ }
+ else if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
enabled = false;
}
else {
++count;
}
- Policy pol;
if (allEmpty) {
- return pol;
+ return false;
}
/* prepare the wildcard-based names */
++count;
continue;
}
-
if (z->findExactNSPolicy(qname, pol)) {
// cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
- return pol;
+ return true;
}
for (const auto& wc : wcNames) {
if (z->findExactNSPolicy(wc, pol)) {
// cerr<<"Had a hit on the nameserver ("<<qname<<") used to process the query"<<endl;
- return pol;
+ return true;
}
}
++count;
}
- return pol;
+ return false;
}
-DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies) const
+bool DNSFilterEngine::getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
{
- Policy pol;
// cout<<"Got question for nameserver IP "<<address.toString()<<endl;
for(const auto& z : d_zones) {
+ if (z->getPriority() >= pol.d_priority) {
+ break;
+ }
const auto zoneName = z->getName();
if(zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
continue;
if(z->findNSIPPolicy(address, pol)) {
// cerr<<"Had a hit on the nameserver ("<<address.toString()<<") used to process the query"<<endl;
- return pol;
+ return true;
}
}
- return pol;
+ return false;
}
-DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies) const
+bool DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
{
// cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
std::vector<bool> zoneEnabled(d_zones.size());
bool allEmpty = true;
for (const auto& z : d_zones) {
bool enabled = true;
- const auto zoneName = z->getName();
- if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+ if (z->getPriority() >= pol.d_priority) {
enabled = false;
- }
- else {
- if (z->hasQNamePolicies() || z->hasClientPolicies()) {
- allEmpty = false;
+ } else {
+ const auto zoneName = z->getName();
+ if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
+ enabled = false;
}
else {
- enabled = false;
+ if (z->hasQNamePolicies() || z->hasClientPolicies()) {
+ allEmpty = false;
+ }
+ else {
+ enabled = false;
+ }
}
}
++count;
}
- Policy pol;
if (allEmpty) {
- return pol;
+ return false;
}
/* prepare the wildcard-based names */
continue;
}
+ if (z->findClientPolicy(ca, pol)) {
+ // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
+ return true;
+ }
+
if (z->findExactQNamePolicy(qname, pol)) {
// cerr<<"Had a hit on the name of the query"<<endl;
- return pol;
+ return true;
}
for (const auto& wc : wcNames) {
if (z->findExactQNamePolicy(wc, pol)) {
// cerr<<"Had a hit on the name of the query"<<endl;
- return pol;
+ return true;
}
}
- if (z->findClientPolicy(ca, pol)) {
- // cerr<<"Had a hit on the IP address ("<<ca.toString()<<") of the client"<<endl;
- return pol;
- }
-
++count;
}
- return pol;
+ return false;
}
-DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies) const
+bool DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& pol) const
{
- Policy pol;
ComboAddress ca;
for (const auto& r : records) {
if (r.d_place != DNSResourceRecord::ANSWER)
continue;
for (const auto& z : d_zones) {
+ if (z->getPriority() >= pol.d_priority) {
+ break;
+ }
const auto zoneName = z->getName();
if (zoneName && discardedPolicies.find(*zoneName) != discardedPolicies.end()) {
continue;
}
if (z->findResponsePolicy(ca, pol)) {
- return pol;
+ return true;
}
}
}
- return pol;
+ return false;
}
void DNSFilterEngine::assureZones(size_t zone)
#include "dnsparser.hh"
#include <map>
#include <unordered_map>
+#include <limits>
/* This class implements a filtering policy that is able to fully implement RPZ, but is not bound to it.
In other words, it is generic enough to support RPZ, but could get its data from other places.
class DNSFilterEngine
{
public:
- enum class PolicyKind { NoAction, Drop, NXDOMAIN, NODATA, Truncate, Custom};
- enum class PolicyType { None, QName, ClientIP, ResponseIP, NSDName, NSIP };
-
+ enum class PolicyKind : uint8_t { NoAction, Drop, NXDOMAIN, NODATA, Truncate, Custom};
+ enum class PolicyType : uint8_t { None, QName, ClientIP, ResponseIP, NSDName, NSIP };
+ typedef uint16_t Priority;
+ static const Priority maximumPriority = std::numeric_limits<Priority>::max();
+
static std::string getKindToString(PolicyKind kind);
static std::string getTypeToString(PolicyType type);
struct Policy
{
- Policy(): d_name(nullptr), d_kind(PolicyKind::NoAction), d_type(PolicyType::None), d_ttl(0)
+ Policy(): d_name(nullptr), d_ttl(0), d_priority(maximumPriority), d_kind(PolicyKind::NoAction), d_type(PolicyType::None)
{
}
- Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<std::string> name=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_name(name), d_kind(kind), d_type(type), d_ttl(ttl)
+ Policy(PolicyKind kind, PolicyType type, int32_t ttl=0, std::shared_ptr<std::string> name=nullptr, const std::vector<std::shared_ptr<DNSRecordContent>>& custom={}): d_custom(custom), d_name(name), d_ttl(ttl), d_priority(maximumPriority), d_kind(kind), d_type(type)
{
}
std::vector<std::shared_ptr<DNSRecordContent>> d_custom;
std::shared_ptr<std::string> d_name; // the name of the policy
- PolicyKind d_kind;
- PolicyType d_type;
/* Yup, we are currently using the same TTL for every record for a given name */
int32_t d_ttl;
+ Priority d_priority;
+ PolicyKind d_kind;
+ PolicyType d_type;
private:
DNSRecord getRecordFromCustom(const DNSName& qname, const std::shared_ptr<DNSRecordContent>& custom) const;
-};
+ };
class Zone {
public:
{
return !d_postpolAddr.empty();
}
-
+ Priority getPriority() const {
+ return d_priority;
+ }
+ void setPriority(Priority p) {
+ d_priority = p;
+ for (auto& pair : d_qpolName) {
+ pair.second.d_priority = p;
+ }
+ for (auto& pair : d_propolName) {
+ pair.second.d_priority = p;
+ }
+ for (auto& pair : d_qpolAddr) {
+ pair.second.d_priority = p;
+ }
+ for (auto& pair : d_propolNSAddr) {
+ pair.second.d_priority = p;
+ }
+ for (auto& pair : d_postpolAddr) {
+ pair.second.d_priority = p;
+ }
+ }
private:
static DNSName maskToRPZ(const Netmask& nm);
static bool findExactNamedPolicy(const std::unordered_map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol);
std::shared_ptr<std::string> d_name;
uint32_t d_serial{0};
uint32_t d_refresh{0};
+ Priority d_priority;
};
DNSFilterEngine();
}
size_t addZone(std::shared_ptr<Zone> newZone)
{
+ newZone->setPriority(d_zones.size());
d_zones.push_back(newZone);
return (d_zones.size() - 1);
}
{
if (newZone) {
assureZones(zoneIdx);
+ newZone->setPriority(zoneIdx);
d_zones[zoneIdx] = newZone;
}
}
- Policy getQueryPolicy(const DNSName& qname, const ComboAddress& nm, const std::unordered_map<std::string,bool>& discardedPolicies) const;
- Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies) const;
- Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies) const;
- Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies) const;
+ bool getQueryPolicy(const DNSName& qname, const ComboAddress& nm, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+ bool getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+ bool getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+ bool getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Policy& policy) const;
+
+ // A few convenience methods for the unit test code
+ Policy getQueryPolicy(const DNSName& qname, const ComboAddress& nm, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+ Policy policy;
+ policy.d_priority = p;
+ getQueryPolicy(qname, nm, discardedPolicies, policy);
+ return policy;
+ }
+
+ Policy getProcessingPolicy(const DNSName& qname, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+ Policy policy;
+ policy.d_priority = p;
+ getProcessingPolicy(qname, discardedPolicies, policy);
+ return policy;
+ }
+
+ Policy getProcessingPolicy(const ComboAddress& address, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+ Policy policy;
+ policy.d_priority = p;
+ getProcessingPolicy(address, discardedPolicies, policy);
+ return policy;
+ }
+
+ Policy getPostPolicy(const vector<DNSRecord>& records, const std::unordered_map<std::string,bool>& discardedPolicies, Priority p) const {
+ Policy policy;
+ policy.d_priority = p;
+ getPostPolicy(records, discardedPolicies, policy);
+ return policy;
+ }
size_t size() const {
return d_zones.size();
}
for(unsigned int n = 0; n+1 < bitmap.size();) {
- unsigned int window=static_cast<unsigned char>(bitmap[n++]);
- unsigned int blen=static_cast<unsigned char>(bitmap[n++]);
+ uint8_t window=static_cast<uint8_t>(bitmap[n++]);
+ uint8_t blen=static_cast<uint8_t>(bitmap[n++]);
// end if zero padding and ensure packet length
- if(window == 0 && blen == 0) {
+ if (window == 0 && blen == 0) {
break;
}
- if(n + blen > bitmap.size()) {
+ if (blen > 32) {
+ throw MOADNSException("NSEC record with invalid bitmap length");
+ }
+
+ if (n + blen > bitmap.size()) {
throw MOADNSException("NSEC record with bitmap length > packet length");
}
}
// Check if the query has a policy attached to it
- if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
- appliedPolicy = luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies);
+ if (wantsRPZ && (appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+ luaconfsLocal->dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_source, sr.d_discardedPolicies, appliedPolicy);
}
// if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
res = -2;
}
dq.validationState = sr.getValidationState();
+ appliedPolicy = sr.d_appliedPolicy;
// During lookup, an NSDNAME or NSIP trigger was hit in RPZ
if (res == -2) { // XXX This block should be macro'd, it is repeated post-resolve.
}
}
- if (wantsRPZ && appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
- appliedPolicy = luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies);
+ if (wantsRPZ && (appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+ luaconfsLocal->dfe.getPostPolicy(ret, sr.d_discardedPolicies, appliedPolicy);
}
if(t_pdl) {
::arg().setCmd("help","Provide a helpful message");
::arg().setCmd("version","Print version string");
::arg().setCmd("config","Output blank configuration");
+ ::arg().setDefaults();
g_log.toConsole(Logger::Info);
::arg().laxParse(argc,argv); // do a lax parse
}
if(::arg().mustDo("config")) {
- cout<<::arg().configstring()<<endl;
+ cout<<::arg().configstring(false, true);
exit(0);
}
string configname=::arg()["config-dir"]+"/"+s_programname+".conf";
cleanSlashes(configname);
- if(!::arg().mustDo("config") && !::arg().mustDo("no-config")) // "config" == print a configuration file
+ if(::arg()["config"] != "default" && !::arg().mustDo("no-config")) // "config" == print a configuration file
::arg().laxFile(configname.c_str());
::arg().laxParse(argc,argv); // reparse so the commandline still wins
}
if(::arg().mustDo("config")) {
- cout<<::arg().configstring()<<endl;
+ string config = ::arg()["config"];
+ if (config == "default") {
+ cout<<::arg().configstring(false, true);
+ } else if (config == "diff") {
+ cout<<::arg().configstring(true, false);
+ } else if (config == "check") {
+ try {
+ if(!::arg().mustDo("no-config"))
+ ::arg().file(configname.c_str());
+ ::arg().parse(argc,argv);
+ exit(0);
+ }
+ catch(const ArgException &A) {
+ cerr<<"Fatal error: "<<A.reason<<endl;
+ exit(1);
+ }
+ } else {
+ cout<<::arg().configstring(true, true);
+ }
exit(0);
}
DynListener::registerFunc("REMOTES", &DLRemotesHandler, "get top remotes");
DynListener::registerFunc("SET",&DLSettingsHandler, "set config variables", "<var> <value>");
DynListener::registerFunc("RETRIEVE",&DLNotifyRetrieveHandler, "retrieve slave domain", "<domain>");
- DynListener::registerFunc("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration");
+ DynListener::registerFunc("CURRENT-CONFIG",&DLCurrentConfigHandler, "retrieve the current configuration", "[diff|default]");
DynListener::registerFunc("LIST-ZONES",&DLListZones, "show list of zones", "[master|slave|native]");
DynListener::registerFunc("TOKEN-LOGIN", &DLTokenLogin, "Login to a PKCS#11 token", "<module> <slot> <pin>");
{
/* blocked NS name */
- auto matchingPolicy = dfe.getProcessingPolicy(nsName, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getProcessingPolicy(nsName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
BOOST_CHECK(zonePolicy == matchingPolicy);
/* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
- matchingPolicy = dfe.getProcessingPolicy(DNSName("sub") + nsName, std::unordered_map<std::string, bool>());
+ matchingPolicy = dfe.getProcessingPolicy(DNSName("sub") + nsName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
BOOST_CHECK(zone->findExactNSPolicy(DNSName("sub") + nsName, zonePolicy) == false);
}
{
/* blocked NS name via wildcard */
- const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("sub.sub.wildcard.wolf."), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("sub.sub.wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
/* looking for wildcard.wolf. should not match *.wildcard-blocked. */
- const auto notMatchingPolicy = dfe.getProcessingPolicy(DNSName("wildcard.wolf."), std::unordered_map<std::string, bool>());
+ const auto notMatchingPolicy = dfe.getProcessingPolicy(DNSName("wildcard.wolf."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(notMatchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
/* a direct lookup would not match */
{
/* allowed NS name */
- const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("ns.bad.rabbit."), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getProcessingPolicy(DNSName("ns.bad.rabbit."), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findExactNSPolicy(DNSName("ns.bad.rabbit."), zonePolicy) == false);
{
/* blocked NS IP */
- const auto matchingPolicy = dfe.getProcessingPolicy(nsIP, std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getProcessingPolicy(nsIP, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSIP);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
DNSFilterEngine::Policy zonePolicy;
{
/* allowed NS IP */
- const auto matchingPolicy = dfe.getProcessingPolicy(ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getProcessingPolicy(ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findNSIPPolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
{
/* blocked qname */
- auto matchingPolicy = dfe.getQueryPolicy(blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zonePolicy == matchingPolicy);
/* but a subdomain should not be blocked (not a wildcard, and this is not suffix domain matching */
- matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ matchingPolicy = dfe.getQueryPolicy(DNSName("sub") + blockedName, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
BOOST_CHECK(zone->findExactQNamePolicy(DNSName("sub") + blockedName, zonePolicy) == false);
}
{
/* blocked NS name via wildcard */
- const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(DNSName("sub.sub.wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
/* looking for wildcard-blocked. should not match *.wildcard-blocked. */
- const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto notMatchingPolicy = dfe.getQueryPolicy(DNSName("wildcard-blocked."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(notMatchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
/* a direct lookup would not match */
{
/* blocked client IP */
- const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), clientIP, std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), clientIP, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
DNSFilterEngine::Policy zonePolicy;
{
/* not blocked */
- const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(DNSName("totally.legit."), ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findClientPolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
DNSRecord dr;
dr.d_type = QType::A;
dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString());
- const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
DNSFilterEngine::Policy zonePolicy;
DNSRecord dr;
dr.d_type = QType::A;
dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, "192.0.2.142");
- const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
DNSFilterEngine::Policy zonePolicy;
BOOST_CHECK(zone->findResponsePolicy(ComboAddress("192.0.2.142"), zonePolicy) == false);
{
const DNSName tstName("bcbsks.com.102.112.2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
}
{
const DNSName tstName("2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
}
// Once fixed the BOOST_WARN should becomes BOOST_CHECK
- const string m("Please fix issue #8321");
+ const string m("Please fix issue #8231");
{
const DNSName tstName("112.2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
}
{
const DNSName tstName("102.112.2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
}
{
const DNSName tstName("com.112.2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_WARN_MESSAGE(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None, m);
BOOST_WARN_MESSAGE(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction, m);
}
{
const DNSName tstName("wcmatch.2o7.net.");
- auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>());
+ auto matchingPolicy = dfe.getQueryPolicy(tstName, address, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Drop);
}
{
/* exact type does not exist, but we have a CNAME */
- const auto matchingPolicy = dfe.getQueryPolicy(bad1, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(bad1, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
auto records = matchingPolicy.getCustomRecords(bad1, QType::A);
{
/* exact type exists */
- const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
{
/* exact type exists */
- const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(bad2, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
{
/* zone 1 should match first */
- const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
auto records = matchingPolicy.getCustomRecords(bad, QType::A);
{
/* zone 2 has an exact match for badUnderWildcard, but the wildcard from the first zone should match first */
- const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>());
+ const auto matchingPolicy = dfe.getQueryPolicy(badUnderWildcard, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
auto records = matchingPolicy.getCustomRecords(badUnderWildcard, QType::A);
{
/* zone 1 should still match if zone 2 has been disabled */
- const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone2->getName()), true}});
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone2->getName()), true}}, DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
auto records = matchingPolicy.getCustomRecords(bad, QType::A);
{
/* if zone 1 is disabled, zone 2 should match */
- const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}});
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
auto records = matchingPolicy.getCustomRecords(bad, QType::A);
{
/* if both zones are disabled, we should not match */
- const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}, {*(zone2->getName()), true}});
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}, {*(zone2->getName()), true}}, DNSFilterEngine::maximumPriority);
BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
}
}
+
+BOOST_AUTO_TEST_CASE(test_multiple_filter_policies_order)
+{
+ DNSFilterEngine dfe;
+
+ auto zone1 = std::make_shared<DNSFilterEngine::Zone>();
+ zone1->setName("Unit test policy 0");
+
+ auto zone2 = std::make_shared<DNSFilterEngine::Zone>();
+ zone2->setName("Unit test policy 1");
+
+ const ComboAddress clientIP("192.0.2.128");
+ const DNSName bad("bad.example.com.");
+ const ComboAddress nsIP("192.0.2.1");
+ const DNSName nsName("ns.bad.wolf.");
+ const ComboAddress responseIP("192.0.2.254");
+
+ zone1->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client1a.example.net.")}));
+ zone1->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden1a.example.net.")}));
+ zone1->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip1a.example.net.")}));
+ zone1->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname1a.example.net.")}));
+ zone1->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response1a.example.net.")}));
+
+ zone2->addClientTrigger(Netmask(clientIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ClientIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "client2a.example.net.")}));
+ zone2->addQNameTrigger(bad, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::QName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "garden2a.example.net.")}));
+ zone2->addNSIPTrigger(Netmask(nsIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsip2a.example.net.")}));
+ zone2->addNSTrigger(nsName, DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::NSDName, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "nsname2a.example.net.")}));
+ zone2->addResponseTrigger(Netmask(responseIP, 32), DNSFilterEngine::Policy(DNSFilterEngine::PolicyKind::Custom, DNSFilterEngine::PolicyType::ResponseIP, 0, nullptr, {DNSRecordContent::mastermake(QType::CNAME, QClass::IN, "response2a.example.net.")}));
+
+ dfe.addZone(zone1);
+ dfe.addZone(zone2);
+ BOOST_CHECK_EQUAL(zone1->getPriority(), 0);
+ BOOST_CHECK_EQUAL(zone2->getPriority(), 1);
+
+ {
+ /* client IP should match before qname */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.128"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ClientIP);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "client1a.example.net.");
+ }
+
+ {
+ /* client IP and qname should match, but zone 1 is disabled and zone2's priority is too high */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.128"), {{*(zone1->getName()), true}}, 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+
+ {
+ /* zone 1 should match first */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
+ }
+
+ {
+ /* zone 1 should still match if we require a priority < 1 */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden1a.example.net.");
+ }
+
+ {
+ /* nothing should match if we require a priority < 0 */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), std::unordered_map<std::string, bool>(), 0);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+
+ {
+ /* if we disable zone 1, zone 2 should match */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::QName);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "garden2a.example.net.");
+ }
+
+ {
+ /* if we disable zone 1, zone 2 should match, except if we require a priority < 1 */
+ const auto matchingPolicy = dfe.getQueryPolicy(bad, ComboAddress("192.0.2.142"), {{*(zone1->getName()), true}}, 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+
+ {
+ /* blocked NS name */
+ auto matchingPolicy = dfe.getProcessingPolicy(nsName, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSDName);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "nsname1a.example.net.");
+ }
+
+ {
+ /* blocked NS name, except policy 1 is disabled and policy2's priority is too high */
+ auto matchingPolicy = dfe.getProcessingPolicy(nsName, {{*(zone1->getName()), true}}, 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+
+ {
+ /* blocked NS IP */
+ const auto matchingPolicy = dfe.getProcessingPolicy(nsIP, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::NSIP);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "nsip1a.example.net.");
+ }
+
+ {
+ /* blocked NS ip, except policy 1 is disabled and policy2's priority is too high */
+ auto matchingPolicy = dfe.getProcessingPolicy(nsIP, {{*(zone1->getName()), true}}, 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+
+ {
+ /* blocked A in the response */
+ DNSRecord dr;
+ dr.d_type = QType::A;
+ dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString());
+ const auto matchingPolicy = dfe.getPostPolicy({dr}, std::unordered_map<std::string, bool>(), DNSFilterEngine::maximumPriority);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::ResponseIP);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::Custom);
+ auto records = matchingPolicy.getCustomRecords(bad, QType::A);
+ BOOST_CHECK_EQUAL(records.size(), 1U);
+ const auto& record = records.at(0);
+ BOOST_CHECK(record.d_type == QType::CNAME);
+ BOOST_CHECK(record.d_class == QClass::IN);
+ auto content = std::dynamic_pointer_cast<CNAMERecordContent>(record.d_content);
+ BOOST_CHECK(content != nullptr);
+ BOOST_CHECK_EQUAL(content->getTarget().toString(), "response1a.example.net.");
+ }
+
+ {
+ /* blocked A in the response, except 1 is disabled and 2's priority is too high */
+ DNSRecord dr;
+ dr.d_type = QType::A;
+ dr.d_content = DNSRecordContent::mastermake(QType::A, QClass::IN, responseIP.toString());
+ const auto matchingPolicy = dfe.getPostPolicy({dr}, {{*(zone1->getName()), true}}, 1);
+ BOOST_CHECK(matchingPolicy.d_type == DNSFilterEngine::PolicyType::None);
+ BOOST_CHECK(matchingPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction);
+ }
+}
the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
process any further RPZ rules.
*/
- if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
+ if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
for (auto const &ns : nameservers) {
- d_appliedPolicy = dfe.getProcessingPolicy(ns.first, d_discardedPolicies);
- if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+ bool match = dfe.getProcessingPolicy(ns.first, d_discardedPolicies, d_appliedPolicy);
+ if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
LOG(", however nameserver "<<ns.first<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
return true;
}
// Traverse all IP addresses for this NS to see if they have an RPN NSIP policy
for (auto const &address : ns.second.first) {
- d_appliedPolicy = dfe.getProcessingPolicy(address, d_discardedPolicies);
- if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+ match = dfe.getProcessingPolicy(address, d_discardedPolicies, d_appliedPolicy);
+ if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
LOG(", however nameserver "<<ns.first<<" IP address "<<address.toString()<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
return true;
}
the only way we can get back here is that it was a 'pass-thru' (NoAction) meaning that we should not
process any further RPZ rules.
*/
- if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
- d_appliedPolicy = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies);
- if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
+ if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+ bool match = dfe.getProcessingPolicy(remoteIP, d_discardedPolicies, d_appliedPolicy);
+ if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) {
LOG(" (blocked by RPZ policy '"+(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")+"')");
return true;
}
nameservers.clear();
for (auto const &nameserver : nsset) {
- if (d_wantsRPZ && d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None) {
- d_appliedPolicy = dfe.getProcessingPolicy(nameserver, d_discardedPolicies);
- if (d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
+ if (d_wantsRPZ && (d_appliedPolicy.d_type == DNSFilterEngine::PolicyType::None || d_appliedPolicy.d_kind == DNSFilterEngine::PolicyKind::NoAction)) {
+ bool match = dfe.getProcessingPolicy(nameserver, d_discardedPolicies, d_appliedPolicy);
+ if (match && d_appliedPolicy.d_kind != DNSFilterEngine::PolicyKind::NoAction) { // client query needs an RPZ response
LOG("however "<<nameserver<<" was blocked by RPZ policy '"<<(d_appliedPolicy.d_name ? *d_appliedPolicy.d_name : "")<<"'"<<endl);
throw PolicyHitException();
}
DLOG(g_log<<"Sending out SOA"<<endl);
DNSZoneRecord soa = makeEditedDNSZRFromSOAData(dk, sd);
outpacket->addRecord(soa);
- if(securedZone) {
+ if(securedZone && outpacket->d_dnssecOk) {
set<DNSName> authSet;
authSet.insert(target);
addRRSigs(dk, signatureDB, authSet, outpacket->getRRS());
}
}
+BOOST_AUTO_TEST_CASE(test_nsec_invalid_bitmap_len) {
+ auto validNSEC = DNSRecordContent::mastermake(QType::NSEC, QClass::IN, "host.example.com. A MX RRSIG NSEC AAAA NSEC3 TYPE1234 TYPE65535");
+ const DNSName powerdnsName("powerdns.com.");
+
+ vector<uint8_t> packet;
+ DNSPacketWriter writer(packet, powerdnsName, QType::NSEC, QClass::IN, 0);
+ writer.getHeader()->qr = 1;
+ writer.startRecord(powerdnsName, QType::NSEC, 100, QClass::IN, DNSResourceRecord::ANSWER, false);
+ validNSEC->toPacket(writer);
+ writer.commit();
+
+ size_t nsecDataPos = sizeof(dnsheader) + powerdnsName.wirelength() + sizeof(uint16_t) + sizeof (uint16_t) + powerdnsName.wirelength() + sizeof(uint16_t) + sizeof (uint16_t) + sizeof(uint32_t) + sizeof(uint16_t);
+ size_t typeBitMapsFieldPos = nsecDataPos + DNSName("host.example.com.").wirelength();
+ auto invalidPacket = packet;
+ /* set the bitmap length value to 33, while the maximum possible value is 32 */
+ invalidPacket.at(typeBitMapsFieldPos + 1) = 33;
+
+ MOADNSParser parser(false, reinterpret_cast<const char*>(packet.data()), packet.size());
+ BOOST_CHECK_THROW(MOADNSParser failParser(false, reinterpret_cast<const char*>(invalidPacket.data()), invalidPacket.size()), MOADNSException);
+}
+
BOOST_AUTO_TEST_CASE(test_nsec3_records_types) {
{
return 0
end
""" % (ProtobufTaggedExtraFieldsTest._requestorId, ProtobufTaggedExtraFieldsTest._deviceId, ProtobufTaggedExtraFieldsTest._deviceName)
+
+class ProtobufRPZTest(TestRecursorProtobuf):
+ """
+ This test makes sure that we correctly export the RPZ applied policy in our protobuf messages
+ """
+
+ _confdir = 'ProtobufRPZ'
+ _config_template = """
+auth-zones=example=configs/%s/example.rpz.zone""" % _confdir
+ _lua_config_file = """
+ protobufServer({"127.0.0.1:%d", "127.0.0.1:%d"}, { logQueries=true, logResponses=true } )
+ rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+ """ % (protobufServersParameters[0].port, protobufServersParameters[1].port, _confdir)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'example.rpz.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+sub.test 3600 IN A 192.0.2.42
+""".format(soa=cls._SOA))
+
+ rpzFilePath = os.path.join(confdir, 'zone.rpz')
+ with open(rpzFilePath, 'w') as rpzZone:
+ rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
+""".format(soa=cls._SOA))
+
+ super(ProtobufRPZTest, cls).generateRecursorConfig(confdir)
+
+ def testA(self):
+ name = 'sub.test.example.'
+ expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
+ query = dns.message.make_query(name, 'A', want_dnssec=True)
+ query.flags |= dns.flags.CD
+ res = self.sendUDPQuery(query)
+ self.assertRRsetInAnswer(res, expected)
+
+ # check the protobuf messages corresponding to the UDP query and answer
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufQuery(msg, dnsmessage_pb2.PBDNSMessage.UDP, query, dns.rdataclass.IN, dns.rdatatype.A, name)
+
+ # then the response
+ msg = self.getFirstProtobufMessage()
+ self.checkProtobufResponse(msg, dnsmessage_pb2.PBDNSMessage.UDP, res)
+ self.checkProtobufPolicy(msg, dnsmessage_pb2.PBDNSMessage.PolicyType.QNAME, 'zone.rpz.')
+ self.assertEquals(len(msg.response.rrs), 1)
+ rr = msg.response.rrs[0]
+ # we have max-cache-ttl set to 15
+ self.checkProtobufResponseRecord(rr, dns.rdataclass.IN, dns.rdatatype.A, name, 15)
+ self.assertEquals(socket.inet_ntop(socket.AF_INET, rr.rdata), '192.0.2.42')
+ self.checkNoRemainingMessage()
def tearDownClass(cls):
cls.tearDownRecursor()
- def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False):
+ def checkBlocked(self, name, shouldBeBlocked=True, adQuery=False, singleCheck=False):
query = dns.message.make_query(name, 'A', want_dnssec=True)
query.flags |= dns.flags.CD
if adQuery:
expected = dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
self.assertRRsetInAnswer(res, expected)
+ if singleCheck:
+ break
- def checkNotBlocked(self, name, adQuery=False):
- self.checkBlocked(name, False, adQuery)
+ def checkNotBlocked(self, name, adQuery=False, singleCheck=False):
+ self.checkBlocked(name, False, adQuery, singleCheck)
def checkCustom(self, qname, qtype, expected):
query = dns.message.make_query(qname, qtype, want_dnssec=True)
"""
_confdir = 'RPZFile'
- _wsPort = 8042
- _wsTimeout = 2
- _wsPassword = 'secretpassword'
- _apiKey = 'secretapikey'
_lua_config_file = """
rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz." })
""" % (_confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone
-webserver=yes
-webserver-port=%d
-webserver-address=127.0.0.1
-webserver-password=%s
-api-key=%s
-""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+""" % (_confdir)
@classmethod
def generateRecursorConfig(cls, confdir):
"""
_confdir = 'RPZFileDefaultPolicy'
- _wsPort = 8042
- _wsTimeout = 2
- _wsPassword = 'secretpassword'
- _apiKey = 'secretapikey'
_lua_config_file = """
rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction })
""" % (_confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone
-webserver=yes
-webserver-port=%d
-webserver-address=127.0.0.1
-webserver-password=%s
-api-key=%s
-""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+""" % (_confdir)
@classmethod
def generateRecursorConfig(cls, confdir):
"""
_confdir = 'RPZFileDefaultPolicyNotOverrideLocal'
- _wsPort = 8042
- _wsTimeout = 2
- _wsPassword = 'secretpassword'
- _apiKey = 'secretapikey'
_lua_config_file = """
rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz.", defpol=Policy.NoAction, defpolOverrideLocalData=false })
""" % (_confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone
-webserver=yes
-webserver-port=%d
-webserver-address=127.0.0.1
-webserver-password=%s
-api-key=%s
-""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+""" % (_confdir)
@classmethod
def generateRecursorConfig(cls, confdir):
self.checkNXD('tc.example.', 'A')
self.checkNotBlocked('drop.example.')
-class RPZOrderingPrecedenceRecursorTesT(RPZRecursorTest):
+class RPZSimpleAuthServer(object):
+
+ def __init__(self, port):
+ self._serverPort = port
+ listener = threading.Thread(name='RPZ Simple Auth Listener', target=self._listener, args=[])
+ listener.setDaemon(True)
+ listener.start()
+
+ def _getAnswer(self, message):
+
+ response = dns.message.make_response(message)
+ response.flags |= dns.flags.AA
+ records = [
+ dns.rrset.from_text('nsip.delegated.example.', 60, dns.rdataclass.IN, dns.rdatatype.A, '192.0.2.42')
+ ]
+
+ response.answer = records
+ return response
+
+ def _listener(self):
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ try:
+ sock.bind(("127.0.0.1", self._serverPort))
+ except socket.error as e:
+ print("Error binding in the RPZ simple auth listener: %s" % str(e))
+ sys.exit(1)
+
+ while True:
+ try:
+ data, addr = sock.recvfrom(4096)
+ message = dns.message.from_wire(data)
+ if len(message.question) != 1:
+ print('Invalid query, qdcount is %d' % (len(message.question)))
+ break
+
+ answer = self._getAnswer(message)
+ if not answer:
+ print('Unable to get a response for %s %d' % (message.question[0].name, message.question[0].rdtype))
+ break
+
+ wire = answer.to_wire()
+ sock.sendto(wire, addr)
+
+ except socket.error as e:
+ print('Error in RPZ simple auth socket: %s' % str(e))
+
+rpzAuthServerPort = 4260
+rpzAuthServer = RPZSimpleAuthServer(rpzAuthServerPort)
+
+class RPZOrderingPrecedenceRecursorTest(RPZRecursorTest):
"""
This test makes sure that the recursor respects the RPZ ordering precedence rules
"""
_confdir = 'RPZOrderingPrecedence'
- _wsPort = 8042
- _wsTimeout = 2
- _wsPassword = 'secretpassword'
- _apiKey = 'secretapikey'
_lua_config_file = """
rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
""" % (_confdir, _confdir)
_config_template = """
auth-zones=example=configs/%s/example.zone
-webserver=yes
-webserver-port=%d
-webserver-address=127.0.0.1
-webserver-password=%s
-api-key=%s
-""" % (_confdir, _wsPort, _wsPassword, _apiKey)
+forward-zones=delegated.example=127.0.0.1:%d
+""" % (_confdir, rpzAuthServerPort)
@classmethod
def generateRecursorConfig(cls, confdir):
authzone.write("""$ORIGIN example.
@ 3600 IN SOA {soa}
sub.test 3600 IN A 192.0.2.42
+passthru-then-blocked-by-higher 3600 IN A 192.0.2.66
+passthru-then-blocked-by-same 3600 IN A 192.0.2.66
+blocked-then-passhtru-by-higher 3600 IN A 192.0.2.100
""".format(soa=cls._SOA))
rpzFilePath = os.path.join(confdir, 'zone.rpz')
rpzZone.write("""$ORIGIN zone.rpz.
@ 3600 IN SOA {soa}
*.test.example.zone.rpz. 60 IN CNAME rpz-passthru.
+32.66.2.0.192.rpz-ip.zone.rpz. 60 IN A 192.0.2.1
+32.100.2.0.192.rpz-ip.zone.rpz. 60 IN CNAME rpz-passthru.
+passthru-then-blocked-by-same.example.zone.rpz. 60 IN CNAME rpz-passthru.
+32.1.0.0.127.rpz-nsip.zone.rpz. 60 IN CNAME rpz-passthru.
""".format(soa=cls._SOA))
rpzFilePath = os.path.join(confdir, 'zone2.rpz')
rpzZone.write("""$ORIGIN zone2.rpz.
@ 3600 IN SOA {soa}
sub.test.example.com.zone2.rpz. 60 IN CNAME .
+passthru-then-blocked-by-higher.example.zone2.rpz. 60 IN CNAME rpz-passthru.
+blocked-then-passhtru-by-higher.example.zone2.rpz. 60 IN A 192.0.2.1
32.42.2.0.192.rpz-ip 60 IN CNAME .
""".format(soa=cls._SOA))
- super(RPZOrderingPrecedenceRecursorTesT, cls).generateRecursorConfig(confdir)
+ super(RPZOrderingPrecedenceRecursorTest, cls).generateRecursorConfig(confdir)
- def testRPZ(self):
+ def testRPZOrderingForQNameAndWhitelisting(self):
# we should first match on the qname (the wildcard, not on the exact name since
# we respect the order of the RPZ zones), see the pass-thru rule
- # and stop RPZ processing. The subsequent rule on the content of the A
- # should therefore not trigger a NXDOMAIN.
+ # and only process RPZ rules of higher precedence.
+ # The subsequent rule on the content of the A should therefore not trigger a NXDOMAIN.
self.checkNotBlocked('sub.test.example.')
+
+ def testRPZOrderingWhitelistedThenBlockedByHigher(self):
+ # we should first match on the qname from the second RPZ zone,
+ # continue the resolution process, and get blocked by the content of the A record
+ # based on the first RPZ zone, whose priority is higher than the second one.
+ self.checkBlocked('passthru-then-blocked-by-higher.example.')
+
+ def testRPZOrderingWhitelistedThenBlockedBySame(self):
+ # we should first match on the qname from the first RPZ zone,
+ # continue the resolution process, and NOT get blocked by the content of the A record
+ # based on the same RPZ zone, since it's not higher.
+ self.checkCustom('passthru-then-blocked-by-same.example.', 'A', dns.rrset.from_text('passthru-then-blocked-by-same.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.66'))
+
+ def testRPZOrderBlockedThenWhitelisted(self):
+ # The qname is first blocked by the second RPZ zone
+ # Then, should the resolution process go on, the A record would be whitelisted
+ # by the first zone.
+ # This is what the RPZ specification requires, but we currently decided that we
+ # don't want to leak queries to malicious DNS servers and waste time if the qname is blacklisted.
+ # We might change our opinion at some point, though.
+ self.checkBlocked('blocked-then-passhtru-by-higher.example.')
+
+ def testRPZOrderDelegate(self):
+ # The IP of the NS we are going to contact is whitelisted (passthru) in zone 1,
+ # so even though the record (192.0.2.42) returned by the server is blacklisted
+ # by zone 2, it should not be blocked.
+ # We only test once because after that the answer is cached, so the NS is not contacted
+ # and the whitelist is not applied (yes, NSIP and NSDNAME are brittle).
+ self.checkNotBlocked('nsip.delegated.example.', singleCheck=True)
+
+class RPZNSIPCustomTest(RPZRecursorTest):
+ """
+ This test makes sure that the recursor handles custom RPZ rules in a NSIP
+ """
+
+ _confdir = 'RPZNSIPCustom'
+ _lua_config_file = """
+ rpzFile('configs/%s/zone.rpz', { policyName="zone.rpz."})
+ rpzFile('configs/%s/zone2.rpz', { policyName="zone2.rpz."})
+ """ % (_confdir, _confdir)
+ _config_template = """
+auth-zones=example=configs/%s/example.zone
+forward-zones=delegated.example=127.0.0.1:%d
+""" % (_confdir, rpzAuthServerPort)
+
+ @classmethod
+ def generateRecursorConfig(cls, confdir):
+ authzonepath = os.path.join(confdir, 'example.zone')
+ with open(authzonepath, 'w') as authzone:
+ authzone.write("""$ORIGIN example.
+@ 3600 IN SOA {soa}
+""".format(soa=cls._SOA))
+
+ rpzFilePath = os.path.join(confdir, 'zone.rpz')
+ with open(rpzFilePath, 'w') as rpzZone:
+ rpzZone.write("""$ORIGIN zone.rpz.
+@ 3600 IN SOA {soa}
+32.1.0.0.127.rpz-nsip.zone.rpz. 60 IN A 192.0.2.1
+""".format(soa=cls._SOA))
+
+ rpzFilePath = os.path.join(confdir, 'zone2.rpz')
+ with open(rpzFilePath, 'w') as rpzZone:
+ rpzZone.write("""$ORIGIN zone2.rpz.
+@ 3600 IN SOA {soa}
+32.1.2.0.192.rpz-ip 60 IN CNAME .
+""".format(soa=cls._SOA))
+
+ super(RPZNSIPCustomTest, cls).generateRecursorConfig(confdir)
+
+ def testRPZDelegate(self):
+ # The IP of the NS we are going to contact should result in a custom record (192.0.2.1) from zone 1,
+ # so even though the record (192.0.2.1) returned by the server is blacklisted
+ # by zone 2, it should not be blocked.
+ # We only test once because after that the answer is cached, so the NS is not contacted
+ # and the whitelist is not applied (yes, NSIP and NSDNAME are brittle).
+ self.checkCustom('nsip.delegated.example.', 'A', dns.rrset.from_text('nsip.delegated.example.', 0, dns.rdataclass.IN, 'A', '192.0.2.1'))