%endif
%if 0%{?rhel} >= 7
Requires(pre): shadow-utils
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 (yet) https://bugzilla.redhat.com/show_bug.cgi?id=1760298
BuildRequires: fstrm-devel
-%endif
%systemd_requires
%endif
--without-protobuf \
--without-net-snmp
%endif
-%if 0%{?rhel} == 7
- --enable-dnstap \
-%endif
%if 0%{?rhel} >= 7
--with-gnutls \
--with-protobuf \
+ --enable-dnstap \
--with-lua=%{lua_implementation} \
--with-libcap \
--with-libsodium \
%if 0%{?rhel} >= 7
BuildRequires: protobuf-compiler
BuildRequires: protobuf-devel
-
-%if 0%{?rhel} == 7
-# No fstrm in EPEL 8 yet
BuildRequires: fstrm-devel
%endif
-%endif
BuildRequires: openssl-devel
BuildRequires: net-snmp-devel
make %{?_smp_mflags} LIBRARY_PATH=/usr/lib64/boost148
%else
--with-protobuf \
+ --enable-dnstap \
--with-libcap \
--with-lua=%{lua_implementation} \
--enable-systemd --with-systemd=%{_unitdir}
Methods
^^^^^^^
+Methods required for different features
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+:Always required: ``initialize``, ``lookup``
+:Master operation: ``list``, ``getUpdatedMasters``, ``setNotified``
+
``initialize``
~~~~~~~~~~~~~~
``createSlaveDomain``
~~~~~~~~~~~~~~~~~~~~~
-
Creates new domain. This method is called when NOTIFY is received and
you are superslaving.
-Mandatory: No Parameters: ip, domain Optional parameters: nameserver,
-account Reply: true for success, false for failure
+ - Mandatory: No
+ - Parameters: ip, domain
+ - Optional parameters: nameserver, account
+ - Reply: true for success, false for failure
Example JSON/RPC
''''''''''''''''
- Mandatory: no
- Parameters: none
-- Reply: array of DomainInfo
+- Reply: array of DomainInfo or at least the ``id``, ``zone``, ``serial`` and ``notified_serial`` fields
Example JSON/RPC
''''''''''''''''
}
}
+void RemoteBackend::alsoNotifies(const DNSName &domain, set<string> *ips)
+{
+ std::vector<std::string> meta;
+ getDomainMetadata(domain, "ALSO-NOTIFY", meta);
+ ips->insert(meta.begin(), meta.end());
+}
+
void RemoteBackend::getUpdatedMasters(vector<DomainInfo>* domains)
{
Json query = Json::object{
bool searchComments(const string &pattern, int maxResults, vector<Comment>& result) override;
void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override;
void getUpdatedMasters(vector<DomainInfo>* domains) override;
+ void alsoNotifies(const DNSName &domain, set<string> *ips) override;
static DNSBackend *maker();
BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"TEST", meta));
}
+BOOST_AUTO_TEST_CASE(test_method_alsoNotifies) {
+ BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", {"192.0.2.1"}));
+ std::set<std::string> alsoNotifies;
+ BOOST_TEST_MESSAGE("Testing alsoNotifies method");
+ be->alsoNotifies(DNSName("unit.test."), &alsoNotifies);
+ BOOST_CHECK_EQUAL(alsoNotifies.size(), 1);
+ if (alsoNotifies.size() > 0)
+ BOOST_CHECK_EQUAL(alsoNotifies.count("192.0.2.1"), 1);
+ BOOST_CHECK(be->setDomainMetadata(DNSName("unit.test."),"ALSO-NOTIFY", std::vector<std::string>()));
+}
+
BOOST_AUTO_TEST_CASE(test_method_getDomainMetadata) {
std::vector<std::string> meta;
BOOST_TEST_MESSAGE("Testing getDomainMetadata method");
{ "snmpAgent", true, "enableTraps [, masterSocket]", "enable `SNMP` support. `enableTraps` is a boolean indicating whether traps should be sent and `masterSocket` an optional string specifying how to connect to the master agent"},
{ "SNMPTrapAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the query description"},
{ "SNMPTrapResponseAction", true, "[reason]", "send an SNMP trap, adding the optional `reason` string as the response description"},
- { "SpoofAction", true, "{ip, ...} ", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
- { "SpoofCNAMEAction", true, "cname", "Forge a response with the specified CNAME value" },
+ { "SpoofAction", true, "ip|list of ips [, options]", "forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA). If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in" },
+ { "SpoofCNAMEAction", true, "cname [, options]", "Forge a response with the specified CNAME value" },
{ "SuffixMatchNodeRule", true, "smn[, quiet]", "Matches based on a group of domain suffixes for rapid testing of membership. Pass true as second parameter to prevent listing of all domains matched" },
{ "TagAction", true, "name, value", "set the tag named 'name' to the given value" },
{ "TagResponseAction", true, "name, value", "set the tag named 'name' to the given value" },
{
dq->dh->rcode = d_rcode;
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
std::string toString() const override
return "set rcode "+std::to_string(d_rcode);
}
+ ResponseConfig d_responseConfig;
private:
uint8_t d_rcode;
};
dq->dh->rcode = (d_rcode & 0xF);
dq->ednsRCode = ((d_rcode & 0xFFF0) >> 4);
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
std::string toString() const override
return "set ercode "+ERCode::to_s(d_rcode);
}
+ ResponseConfig d_responseConfig;
private:
uint8_t d_rcode;
};
char* dest = ((char*)dq->dh) + dq->len;
dq->dh->qr = true; // for good measure
- dq->dh->ra = dq->dh->rd; // for good measure
- dq->dh->ad = false;
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
dq->dh->ancount = 0;
dq->dh->arcount = 0; // for now, forget about your EDNS, we're marching over it
dq->du->setHTTPResponse(d_code, d_body, d_contentType);
dq->dh->qr = true; // for good measure
+ setResponseHeadersFromConfig(*dq->dh, d_responseConfig);
return Action::HeaderModify;
}
{
return "return an HTTP status of " + std::to_string(d_code);
}
+
+ ResponseConfig d_responseConfig;
private:
std::string d_body;
std::string d_contentType;
});
}
+typedef std::unordered_map<std::string, boost::variant<bool> > responseParams_t;
+
+static void parseResponseConfig(boost::optional<responseParams_t> vars, ResponseConfig& config)
+{
+ if (vars) {
+ if (vars->count("aa")) {
+ config.setAA = boost::get<bool>((*vars)["aa"]);
+ }
+ if (vars->count("ad")) {
+ config.setAD = boost::get<bool>((*vars)["ad"]);
+ }
+ if (vars->count("ra")) {
+ config.setRA = boost::get<bool>((*vars)["ra"]);
+ }
+ }
+}
+
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config)
+{
+ if (config.setAA) {
+ dh.aa = *config.setAA;
+ }
+ if (config.setAD) {
+ dh.ad = *config.setAD;
+ }
+ else {
+ dh.ad = false;
+ }
+ if (config.setRA) {
+ dh.ra = *config.setRA;
+ }
+ else {
+ dh.ra = dh.rd; // for good measure
+ }
+}
+
void setupLuaActions()
{
g_lua.writeFunction("newRuleAction", [](luadnsrule_t dnsrule, std::shared_ptr<DNSAction> action, boost::optional<luaruleparams_t> params) {
return std::shared_ptr<DNSAction>(new QPSPoolAction(limit, a));
});
- g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b ) {
+ g_lua.writeFunction("SpoofAction", [](boost::variant<std::string,vector<pair<int, std::string>>> inp, boost::optional<std::string> b, boost::optional<responseParams_t> vars ) {
vector<ComboAddress> addrs;
if(auto s = boost::get<std::string>(&inp))
addrs.push_back(ComboAddress(*s));
for(const auto& a: v)
addrs.push_back(ComboAddress(a.second));
}
- if(b)
+ if(b) {
addrs.push_back(ComboAddress(*b));
- return std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+ }
+
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(addrs));
+ auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+ parseResponseConfig(vars, sa->d_responseConfig);
+ return ret;
});
- g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a) {
- return std::shared_ptr<DNSAction>(new SpoofAction(a));
+ g_lua.writeFunction("SpoofCNAMEAction", [](const std::string& a, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new SpoofAction(a));
+ ResponseConfig responseConfig;
+ parseResponseConfig(vars, responseConfig);
+ auto sa = std::dynamic_pointer_cast<SpoofAction>(ret);
+ sa->d_responseConfig = responseConfig;
+ return ret;
});
g_lua.writeFunction("DropAction", []() {
return std::shared_ptr<DNSResponseAction>(new LogResponseAction(fname ? *fname : "", append ? *append : false, buffered ? *buffered : false, verboseOnly ? *verboseOnly : true, includeTimestamp ? *includeTimestamp : false));
});
- g_lua.writeFunction("RCodeAction", [](uint8_t rcode) {
- return std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+ g_lua.writeFunction("RCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new RCodeAction(rcode));
+ auto rca = std::dynamic_pointer_cast<RCodeAction>(ret);
+ parseResponseConfig(vars, rca->d_responseConfig);
+ return ret;
});
- g_lua.writeFunction("ERCodeAction", [](uint8_t rcode) {
- return std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+ g_lua.writeFunction("ERCodeAction", [](uint8_t rcode, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new ERCodeAction(rcode));
+ auto erca = std::dynamic_pointer_cast<ERCodeAction>(ret);
+ parseResponseConfig(vars, erca->d_responseConfig);
+ return ret;
});
g_lua.writeFunction("SkipCacheAction", []() {
});
#ifdef HAVE_DNS_OVER_HTTPS
- g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType) {
- return std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+ g_lua.writeFunction("HTTPStatusAction", [](uint16_t status, std::string body, boost::optional<std::string> contentType, boost::optional<responseParams_t> vars) {
+ auto ret = std::shared_ptr<DNSAction>(new HTTPStatusAction(status, body, contentType ? *contentType : ""));
+ auto hsa = std::dynamic_pointer_cast<HTTPStatusAction>(ret);
+ parseResponseConfig(vars, hsa->d_responseConfig);
+ return ret;
});
#endif /* HAVE_DNS_OVER_HTTPS */
return (bool)dh.rd;
});
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setRA", [](dnsheader& dh, bool v) {
+ dh.ra=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getRA", [](dnsheader& dh) {
+ return (bool)dh.ra;
+ });
+
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setAD", [](dnsheader& dh, bool v) {
+ dh.ad=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getAD", [](dnsheader& dh) {
+ return (bool)dh.ad;
+ });
+
+ g_lua.registerFunction<void(dnsheader::*)(bool)>("setAA", [](dnsheader& dh, bool v) {
+ dh.aa=v;
+ });
+
+ g_lua.registerFunction<bool(dnsheader::*)()>("getAA", [](dnsheader& dh) {
+ return (bool)dh.aa;
+ });
+
g_lua.registerFunction<void(dnsheader::*)(bool)>("setCD", [](dnsheader& dh, bool v) {
dh.cd=v;
});
*/
#pragma once
+struct ResponseConfig
+{
+ boost::optional<bool> setAA{boost::none};
+ boost::optional<bool> setAD{boost::none};
+ boost::optional<bool> setRA{boost::none};
+};
+void setResponseHeadersFromConfig(dnsheader& dh, const ResponseConfig& config);
+
class LuaAction : public DNSAction
{
public:
}
return ret;
}
+
+
+ ResponseConfig d_responseConfig;
private:
std::vector<ComboAddress> d_addrs;
DNSName d_cname;
case DNSAction::Action::Truncate:
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
break;
case DNSAction::Action::HeaderModify:
vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
}
else {
vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toLogString());
dq.dh->tc = true;
dq.dh->qr = true;
+ dq.dh->ra = dq.dh->rd;
+ dq.dh->aa = false;
+ dq.dh->ad = false;
return true;
}
else {
:param int v6: The IPv6 netmask length
-.. function:: ERCodeAction(rcode)
+.. function:: ERCodeAction(rcode [, options])
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
Reply immediately by turning the query into a response with the specified EDNS extended ``rcode``.
``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
:param int rcode: The extended RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
-.. function:: HTTPStatusAction(status, body, contentType="")
+.. function:: HTTPStatusAction(status, body, contentType="" [, options])
.. versionadded:: 1.4.0
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
+
Return an HTTP response with a status code of ''status''. For HTTP redirects, ''body'' should be the redirect URL.
:param int status: The HTTP status code to return.
:param string body: The body of the HTTP response, or a URL if the status code is a redirect (3xx).
:param string contentType: The HTTP Content-Type header to return for a 200 response, ignored otherwise. Default is ''application/dns-message''.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: KeyValueStoreLookupAction(kvs, lookupKey, destinationTag)
:param int maxqps: The QPS limit for that pool
:param string poolname: The name of the pool
-.. function:: RCodeAction(rcode)
+.. function:: RCodeAction(rcode [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Reply immediately by turning the query into a response with the specified ``rcode``.
``rcode`` can be specified as an integer or as one of the built-in :ref:`DNSRCode`.
:param int rcode: The RCODE to respond with.
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: RemoteLogAction(remoteLogger[, alterFunction [, options]])
:param string message: The message to include
-.. function:: SpoofAction(ip[, ip[...]])
- SpoofAction(ips)
+.. function:: SpoofAction(ip [, options])
+ SpoofAction(ips [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Forge a response with the specified IPv4 (for an A query) or IPv6 (for an AAAA) addresses.
If you specify multiple addresses, all that match the query type (A, AAAA or ANY) will get spoofed in.
:param string ip: An IPv4 and/or IPv6 address to spoof
:param {string} ips: A table of IPv4 and/or IPv6 addresses to spoof
+ :param table options: A table with key: value pairs with options.
+
+ Options:
-.. function:: SpoofCNAMEAction(cname)
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
+
+.. function:: SpoofCNAMEAction(cname [, options])
+
+ .. versionchanged:: 1.5.0
+ Added the optional parameter ``options``.
Forge a response with the specified CNAME value.
:param string cname: The name to respond with
+ :param table options: A table with key: value pairs with options.
+
+ Options:
+
+ * ``aa``: bool - Set the AA bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ad``: bool - Set the AD bit to this value (true means the bit is set, false means it's cleared). Default is to clear it.
+ * ``ra``: bool - Set the RA bit to this value (true means the bit is set, false means it's cleared). Default is to copy the value of the RD bit from the incoming query.
.. function:: TagAction(name, value)
}
#endif /* NOD_ENABLED */
+static void checkSocketDir(void)
+{
+ struct stat st;
+ string dir(::arg()["socket-dir"]);
+ string msg;
+
+ if (stat(dir.c_str(), &st) == -1) {
+ msg = "it does not exist or cannot access";
+ }
+ else if (!S_ISDIR(st.st_mode)) {
+ msg = "it is not a directory";
+ }
+ else if (access(dir.c_str(), R_OK | W_OK | X_OK) != 0) {
+ msg = "cannot read, write or search";
+ } else {
+ return;
+ }
+ g_log << Logger::Error << "Problem with socket directory " << dir << ": " << msg << "; see https://docs.powerdns.com/recursor/upgrade.html#x-to-4-3-0-or-master" << endl;
+ _exit(1);
+}
+
static int serviceMain(int argc, char*argv[])
{
g_log.setName(s_programname);
g_log<<Logger::Info<<"Chrooted to '"<<::arg()["chroot"]<<"'"<<endl;
}
+ checkSocketDir();
+
s_pidfname=::arg()["socket-dir"]+"/"+s_programname+".pid";
if(!s_pidfname.empty())
unlink(s_pidfname.c_str()); // remove possible old pid file
"""
name = 'anytruncatetcp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'ANY', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
"""
name = 'andnot.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'aorudp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'AAAA', 'IN')
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
3600,
"""
name = 'aorudp.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'qpsnone.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'nmgrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'dstportrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# more than 6 labels, the query should be refused
name = 'not.ok.labelscount.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# less than 5 labels, the query should be refused
name = 'labelscountadvanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# too short, the query should be refused
name = 'short.qnamewirelength.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# too long, the query should be refused
name = 'toolongtobevalid.qnamewirelength.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
# this one should be refused
name = 'notincludedir.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'tc.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
3600,
query = dns.message.make_query(name, 'A', 'IN')
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
+ expectedResponse.flags |= dns.flags.RA
for method in ("sendUDPQuery", "sendTCPQuery"):
sender = getattr(self, method)
addAction(EDNSVersionRule(0), ERCodeAction(DNSRCode.BADVERS))
"""
- def testDropped(self):
+ def testBadVers(self):
"""
- Advanced: A question with ECS version larger than 0 is dropped
+ Advanced: A question with ECS version larger than 0 yields BADVERS
"""
name = 'ednsversionrule.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=1)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.BADVERS)
"""
name = 'any.tests.powerdns.com.'
query = dns.message.make_query(name, 'ANY', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'evil4242.regex.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'nameAndQtype.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.NOTIMP)
"""
name = 'dnsname.addaction.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
for name in ['dnsname-table{}.addaction.powerdns.com.'.format(i) for i in range(1,2)]:
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'refused.doh.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
query.id = 0
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'qrateactiontruncated.dynblocks.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
60,
"""
name = 'no-edns.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'no-edns.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-no-do.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-no-do.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-do.rcode.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-do.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=True)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
name = 'edns-options.rcode.edns-self.tests.powerdns.com.'
ecso = clientsubnetoption.ClientSubnetOption('127.0.0.1', 24)
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-options.tc.edns-self.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, options=[ecso], payload=512, want_dnssec=True)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query, our_payload=1042)
expectedResponse.flags |= dns.flags.TC
"""
name = 'edns-no-do.rcode.edns-self-disabled.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
name = 'edns-no-do.tc.edns-self-disabled.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True, payload=4096, want_dnssec=False)
+ # dnsdist sets RA = RD for TC responses
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.flags |= dns.flags.TC
"""
name = 'refuseemptyar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'refusetwoar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
query.additional.append(dns.rrset.from_text(name,
3600,
dns.rdataclass.IN,
"""
name = 'refusenoan.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
"""
name = 'refusefouran.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
rrset = dns.rrset.from_text_list(name,
3600,
dns.rdataclass.IN,
dns.rdatatype.NS,
'ns.tests.powerdns.com.')
query.authority.append(rrset)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
expectedResponse.authority.append(rrset)
"""
name = 'refuseoptinar.recordscount.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)
_config_template = """
addAction(makeRule("spoofaction.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1"))
+ addAction(makeRule("spoofaction-aa.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {aa=true}))
+ addAction(makeRule("spoofaction-ad.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ad=true}))
+ addAction(makeRule("spoofaction-ra.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=true}))
+ addAction(makeRule("spoofaction-nora.spoofing.tests.powerdns.com."), SpoofAction("192.0.2.1", "2001:DB8::1", {ra=false}))
addAction(makeRule("cnamespoofaction.spoofing.tests.powerdns.com."), SpoofCNAMEAction("cnameaction.spoofing.tests.powerdns.com."))
addAction("multispoof.spoofing.tests.powerdns.com", SpoofAction({"192.0.2.1", "192.0.2.2", "2001:DB8::1", "2001:DB8::2"}))
newServer{address="127.0.0.1:%s"}
self.assertTrue(receivedResponse)
self.assertEquals(expectedResponse, receivedResponse)
+ def testSpoofActionSetAA(self):
+ """
+ Spoofing: Spoof via Action, setting AA=1
+ """
+ name = 'spoofaction-aa.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.AA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetAD(self):
+ """
+ Spoofing: Spoof via Action, setting AD=1
+ """
+ name = 'spoofaction-ad.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.AD
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetRA(self):
+ """
+ Spoofing: Spoof via Action, setting RA=1
+ """
+ name = 'spoofaction-ra.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ # dnsdist set RA = RD for spoofed responses
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.RA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
+ def testSpoofActionSetNoRA(self):
+ """
+ Spoofing: Spoof via Action, setting RA=0
+ """
+ name = 'spoofaction-nora.spoofing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags &= ~dns.flags.RA
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ '2001:DB8::1')
+ expectedResponse.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (_, receivedResponse) = sender(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ self.assertEquals(expectedResponse, receivedResponse)
+
class TestSpoofingLuaSpoof(DNSDistTest):
_config_template = """
"""
name = 'refused.tcpka.tests.powerdns.com.'
query = dns.message.make_query(name, 'A', 'IN')
+ query.flags &= ~dns.flags.RD
expectedResponse = dns.message.make_response(query)
expectedResponse.set_rcode(dns.rcode.REFUSED)