* RE2Rule on query name (optional)
* Packet requests DNSSEC processing
* Query received over UDP or TCP
+ * Opcode (OpcodeRule)
+ * Number of entries in a given section (RecordsCountRule)
+ * Number of entries of a specific type in a given section (RecordsTypeCountRule)
+ * Presence of trailing data (TrailingDataRule)
Special rules are:
* a MaxQPSRule
* a NetmaskGroupRule
* a NotRule
+ * an OpcodeRule
* an OrRule
* a QClassRule
* a QTypeRule
* a RegexRule
* a RE2Rule
+ * a RecordsCountRule
+ * a RecordsTypeCountRule
* a SuffixMatchNodeRule
* a TCPRule
+ * a TrailingDataRule
Some specific actions do not stop the processing when they match, contrary to all other actions:
* `NetmaskGroupRule()`: matches traffic from the specified network range
* `NotRule()`: matches if the sub-rule does not match
* `OrRule()`: matches if at least one of the sub-rules matches
+ * `OpcodeRule()`: matches queries with the specified opcode
* `QClassRule(qclass)`: matches queries with the specified qclass (numeric)
* `QTypeRule(qtype)`: matches queries with the specified qtype
* `RegexRule(regex)`: matches the query name against the supplied regex
+ * `RecordsCountRule(section, minCount, maxCount)`: matches if there is at least `minCount` and at most `maxCount` records in the `section` section
+ * `RecordsTypeCountRule(section, type, minCount, maxCount)`: matches if there is at least `minCount` and at most `maxCount` records of type `type` in the `section` section
+ * `RE2Rule(regex)`: matches the query name against the supplied regex using the RE2 engine
* `SuffixMatchNodeRule(smn, [quiet-bool])`: 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.
* `TCPRule(tcp)`: matches question received over TCP if `tcp` is true, over UDP otherwise
+ * `TrailingDataRule()`: matches if the query has trailing data
* Rule management related:
* `getAction(num)`: returns the Action associate with rule 'num'.
* `showRules()`: show all defined rules (Pool, Block, QPS, addAnyTCRule)
* member `dh`: DNSHeader
* member `len`: the question length
* member `localaddr`: ComboAddress of the local bind this question was received on
+ * member `opcode`: the question opcode
* member `qname`: DNSName of this question
* member `qclass`: QClass (as an unsigned integer) of this question
* member `qtype`: QType (as an unsigned integer) of this question
{"Delay", (int)DNSAction::Action::Delay}}
);
- g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
+ g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
{"None",(int)DNSResponseAction::Action::None}}
);
+ g_lua.writeVariable("DNSClass", std::unordered_map<string,int>{
+ {"IN", QClass::IN },
+ {"CHAOS", QClass::CHAOS },
+ {"NONE", QClass::NONE },
+ {"ANY", QClass::ANY }
+ });
+
+ g_lua.writeVariable("DNSOpcode", std::unordered_map<string,int>{
+ {"Query", Opcode::Query },
+ {"IQuery", Opcode::IQuery },
+ {"Status", Opcode::Status },
+ {"Notify", Opcode::Notify },
+ {"Update", Opcode::Update }
+ });
+
+ g_lua.writeVariable("DNSSection", std::unordered_map<string,int>{
+ {"Question", 0 },
+ {"Answer", 1 },
+ {"Authority", 2 },
+ {"Additional",3 }
+ });
+
+ vector<pair<string, int> > rcodes = {{"NOERROR", RCode::NoError },
+ {"FORMERR", RCode::FormErr },
+ {"SERVFAIL", RCode::ServFail },
+ {"NXDOMAIN", RCode::NXDomain },
+ {"NOTIMP", RCode::NotImp },
+ {"REFUSED", RCode::Refused },
+ {"YXDOMAIN", RCode::YXDomain },
+ {"YXRRSET", RCode::YXRRSet },
+ {"NXRRSET", RCode::NXRRSet },
+ {"NOTAUTH", RCode::NotAuth },
+ {"NOTZONE", RCode::NotZone }
+ };
vector<pair<string, int> > dd;
for(const auto& n : QType::names)
dd.push_back({n.first, n.second});
+ for(const auto& n : rcodes)
+ dd.push_back({n.first, n.second});
g_lua.writeVariable("dnsdist", dd);
g_lua.writeFunction("newServer",
}
return std::shared_ptr<DNSRule>(new QTypeRule(qtype));
});
- g_lua.writeFunction("QClassRule", [](int c) {
+ g_lua.writeFunction("QClassRule", [](int c) {
return std::shared_ptr<DNSRule>(new QClassRule(c));
});
+ g_lua.writeFunction("OpcodeRule", [](uint8_t code) {
+ return std::shared_ptr<DNSRule>(new OpcodeRule(code));
+ });
g_lua.writeFunction("AndRule", [](vector<pair<int, std::shared_ptr<DNSRule> > >a) {
return std::shared_ptr<DNSRule>(new AndRule(a));
return std::shared_ptr<DNSRule>(new NotRule(rule));
});
- g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea)
+ g_lua.writeFunction("RecordsCountRule", [](uint8_t section, uint16_t minCount, uint16_t maxCount) {
+ return std::shared_ptr<DNSRule>(new RecordsCountRule(section, minCount, maxCount));
+ });
+
+ g_lua.writeFunction("RecordsTypeCountRule", [](uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount) {
+ return std::shared_ptr<DNSRule>(new RecordsTypeCountRule(section, type, minCount, maxCount));
+ });
+
+ g_lua.writeFunction("TrailingDataRule", []() {
+ return std::shared_ptr<DNSRule>(new TrailingDataRule());
+ });
+
+ g_lua.writeFunction("addAction", [](luadnsrule_t var, std::shared_ptr<DNSAction> ea)
{
setLuaSideEffect();
auto rule=makeRule(var);
/* DNSDist DNSQuestion */
g_lua.registerMember("dh", &DNSQuestion::dh);
g_lua.registerMember<uint16_t (DNSQuestion::*)>("len", [](const DNSQuestion& dq) -> uint16_t { return dq.len; }, [](DNSQuestion& dq, uint16_t newlen) { dq.len = newlen; });
+ g_lua.registerMember<uint8_t (DNSQuestion::*)>("opcode", [](const DNSQuestion& dq) -> uint8_t { return dq.dh->opcode; }, [](DNSQuestion& dq, uint8_t newOpcode) { (void) newOpcode; });
g_lua.registerMember<size_t (DNSQuestion::*)>("size", [](const DNSQuestion& dq) -> size_t { return dq.size; }, [](DNSQuestion& dq, size_t newSize) { (void) newSize; });
g_lua.registerMember<bool (DNSQuestion::*)>("tcp", [](const DNSQuestion& dq) -> bool { return dq.tcp; }, [](DNSQuestion& dq, bool newTcp) { (void) newTcp; });
g_lua.registerMember<bool (DNSQuestion::*)>("skipCache", [](const DNSQuestion& dq) -> bool { return dq.skipCache; }, [](DNSQuestion& dq, bool newSkipCache) { dq.skipCache = newSkipCache; });
-
-- listen for console connection with the given secret key
controlSocket("0.0.0.0")
setKey("MXNeLFWHUe4363BBKrY06cAsH8NWNb+Se2eXU5+Bb74=")
-- addAction(AndRule({QTypeRule("ANY"), TCPRule(true)}), TCAction())
-- return 'not implemented' for qtype != A over UDP
--- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(4))
+-- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(dnsdist.NOTIMPL))
-- return 'not implemented' for qtype == A OR received over UDP
--- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(4))
+-- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(dnsdist.NOTIMPL))
-- log all queries to a 'dndist.log' file, in text-mode (not binary)
-- addAction(AllRule(), LogAction("dnsdist.log", false))
-- drop all queries for the CHAOS class
-- addAction(QClassRule(3), DropAction())
+-- addAction(QClassRule(DNSClass.CHAOS), DropAction())
+
+-- drop all queries with the UPDATE opcode
+-- addAction(OpcodeRule(DNSOpcode.Update), DropAction())
+
+-- refuse all queries not having exactly one question
+-- addAction(NotRule(RecordsCountRule(DNSSection.Question, 1, 1)), RCodeAction(dnsdist.REFUSED))
-- return 'refused' for domains matching the regex evil[0-9]{4,}.powerdns.com$
--- addAction(RegexRule("evil[0-9]{4,}\\.powerdns\\.com$"), RCodeAction(5))
+-- addAction(RegexRule("evil[0-9]{4,}\\.powerdns\\.com$"), RCodeAction(dnsdist.REFUSED))
-- spoof responses for A, AAAA and ANY for spoof.powerdns.com.
-- A queries will get 192.0.2.1, AAAA 2001:DB8::1 and ANY both
tmp = htonl(tmp);
memcpy(d_packet + d_offset-4, (const char*)&tmp, sizeof(tmp));
}
+ uint32_t getOffset() const
+ {
+ return d_offset;
+ }
private:
void moveOffset(uint16_t by)
{
}
return result;
}
+
+uint32_t getDNSPacketLength(const char* packet, size_t length)
+{
+ uint32_t result = length;
+ if(length < sizeof(dnsheader)) {
+ return result;
+ }
+ try
+ {
+ const dnsheader* dh = (const dnsheader*) packet;
+ DNSPacketMangler dpm(const_cast<char*>(packet), length);
+
+ const uint16_t qdcount = ntohs(dh->qdcount);
+ for(size_t n = 0; n < qdcount; ++n) {
+ dpm.skipLabel();
+ dpm.skipBytes(4); // qtype, qclass
+ }
+ const size_t numrecords = ntohs(dh->ancount) + ntohs(dh->nscount) + ntohs(dh->arcount);
+ for(size_t n = 0; n < numrecords; ++n) {
+ dpm.skipLabel();
+
+ /* const uint16_t dnstype */ dpm.get16BitInt();
+ /* uint16_t dnsclass */ dpm.get16BitInt();
+ /* const uint32_t ttl */ dpm.get32BitInt();
+ dpm.skipRData();
+ }
+ result = dpm.getOffset();
+ }
+ catch(...)
+ {
+ }
+ return result;
+}
+
+uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type)
+{
+ uint16_t result = 0;
+ if(length < sizeof(dnsheader)) {
+ return result;
+ }
+ try
+ {
+ const dnsheader* dh = (const dnsheader*) packet;
+ DNSPacketMangler dpm(const_cast<char*>(packet), length);
+
+ const uint16_t qdcount = ntohs(dh->qdcount);
+ for(size_t n = 0; n < qdcount; ++n) {
+ dpm.skipLabel();
+ if (section == 0) {
+ uint16_t dnstype = dpm.get16BitInt();
+ if (dnstype == type) {
+ result++;
+ }
+ dpm.skipBytes(2); // qclass
+ } else {
+ dpm.skipBytes(4); // qtype, qclass
+ }
+ }
+ const uint16_t ancount = ntohs(dh->ancount);
+ for(size_t n = 0; n < ancount; ++n) {
+ dpm.skipLabel();
+ if (section == 1) {
+ uint16_t dnstype = dpm.get16BitInt();
+ if (dnstype == type) {
+ result++;
+ }
+ dpm.skipBytes(2); // qclass
+ } else {
+ dpm.skipBytes(4); // qtype, qclass
+ }
+ /* const uint32_t ttl */ dpm.get32BitInt();
+ dpm.skipRData();
+ }
+ const uint16_t nscount = ntohs(dh->nscount);
+ for(size_t n = 0; n < nscount; ++n) {
+ dpm.skipLabel();
+ if (section == 2) {
+ uint16_t dnstype = dpm.get16BitInt();
+ if (dnstype == type) {
+ result++;
+ }
+ dpm.skipBytes(2); // qclass
+ } else {
+ dpm.skipBytes(4); // qtype, qclass
+ }
+ /* const uint32_t ttl */ dpm.get32BitInt();
+ dpm.skipRData();
+ }
+ const uint16_t arcount = ntohs(dh->arcount);
+ for(size_t n = 0; n < arcount; ++n) {
+ dpm.skipLabel();
+ if (section == 3) {
+ uint16_t dnstype = dpm.get16BitInt();
+ if (dnstype == type) {
+ result++;
+ }
+ dpm.skipBytes(2); // qclass
+ } else {
+ dpm.skipBytes(4); // qtype, qclass
+ }
+ /* const uint32_t ttl */ dpm.get32BitInt();
+ dpm.skipRData();
+ }
+ }
+ catch(...)
+ {
+ }
+ return result;
+}
void ageDNSPacket(char* packet, size_t length, uint32_t seconds);
void ageDNSPacket(std::string& packet, uint32_t seconds);
uint32_t getDNSPacketMinTTL(const char* packet, size_t length);
+uint32_t getDNSPacketLength(const char* packet, size_t length);
+uint16_t getRecordsOfTypeCount(const char* packet, size_t length, uint8_t section, uint16_t type);
template<typename T>
std::shared_ptr<T> getRR(const DNSRecord& dr)
#include "lock.hh"
#include "remote_logger.hh"
#include "dnsdist-protobuf.hh"
+#include "dnsparser.hh"
class MaxQPSIPRule : public DNSRule
{
uint16_t d_qclass;
};
+class OpcodeRule : public DNSRule
+{
+public:
+ OpcodeRule(uint8_t opcode) : d_opcode(opcode)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ return d_opcode == dq->dh->opcode;
+ }
+ string toString() const override
+ {
+ return "opcode=="+d_opcode;
+ }
+private:
+ uint8_t d_opcode;
+};
class TCPRule : public DNSRule
{
shared_ptr<DNSRule> d_rule;
};
+class RecordsCountRule : public DNSRule
+{
+public:
+ RecordsCountRule(uint8_t section, uint16_t minCount, uint16_t maxCount): d_minCount(minCount), d_maxCount(maxCount), d_section(section)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ uint16_t count = 0;
+ switch(d_section) {
+ case 0:
+ count = ntohs(dq->dh->qdcount);
+ break;
+ case 1:
+ count = ntohs(dq->dh->ancount);
+ break;
+ case 2:
+ count = ntohs(dq->dh->nscount);
+ break;
+ case 3:
+ count = ntohs(dq->dh->arcount);
+ break;
+ }
+ return count >= d_minCount && count <= d_maxCount;
+ }
+ string toString() const override
+ {
+ string section;
+ switch(d_section) {
+ case 0:
+ section = "QD";
+ break;
+ case 1:
+ section = "AN";
+ break;
+ case 2:
+ section = "NS";
+ break;
+ case 3:
+ section = "AR";
+ break;
+ }
+ return std::to_string(d_minCount) + " <= records in " + section + " <= "+ std::to_string(d_maxCount);
+ }
+private:
+ uint16_t d_minCount;
+ uint16_t d_maxCount;
+ uint8_t d_section;
+};
+
+class RecordsTypeCountRule : public DNSRule
+{
+public:
+ RecordsTypeCountRule(uint8_t section, uint16_t type, uint16_t minCount, uint16_t maxCount): d_type(type), d_minCount(minCount), d_maxCount(maxCount), d_section(section)
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ uint16_t count = 0;
+ switch(d_section) {
+ case 0:
+ count = ntohs(dq->dh->qdcount);
+ break;
+ case 1:
+ count = ntohs(dq->dh->ancount);
+ break;
+ case 2:
+ count = ntohs(dq->dh->nscount);
+ break;
+ case 3:
+ count = ntohs(dq->dh->arcount);
+ break;
+ }
+ if (count < d_minCount || count > d_maxCount) {
+ return false;
+ }
+ count = getRecordsOfTypeCount(reinterpret_cast<const char*>(dq->dh), dq->len, d_section, d_type);
+ return count >= d_minCount && count <= d_maxCount;
+ }
+ string toString() const override
+ {
+ string section;
+ switch(d_section) {
+ case 0:
+ section = "QD";
+ break;
+ case 1:
+ section = "AN";
+ break;
+ case 2:
+ section = "NS";
+ break;
+ case 3:
+ section = "AR";
+ break;
+ }
+ return std::to_string(d_minCount) + " <= " + QType(d_type).getName() + " records in " + section + " <= "+ std::to_string(d_maxCount);
+ }
+private:
+ uint16_t d_type;
+ uint16_t d_minCount;
+ uint16_t d_maxCount;
+ uint8_t d_section;
+};
+
+class TrailingDataRule : public DNSRule
+{
+public:
+ TrailingDataRule()
+ {
+ }
+ bool matches(const DNSQuestion* dq) const override
+ {
+ uint16_t length = getDNSPacketLength(reinterpret_cast<const char*>(dq->dh), dq->len);
+ return length < dq->len;
+ }
+ string toString() const override
+ {
+ return "trailing data";
+ }
+};
class DropAction : public DNSAction
{
return response
@classmethod
- def UDPResponder(cls, port):
+ def UDPResponder(cls, port, ignoreTrailing=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
sock.bind(("127.0.0.1", port))
while True:
data, addr = sock.recvfrom(4096)
- request = dns.message.from_wire(data)
+ request = dns.message.from_wire(data, ignore_trailing=ignoreTrailing)
response = cls._getResponse(request)
+
if not response:
continue
sock.close()
@classmethod
- def TCPResponder(cls, port):
+ def TCPResponder(cls, port, ignoreTrailing=False):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
try:
data = conn.recv(2)
(datalen,) = struct.unpack("!H", data)
data = conn.recv(datalen)
- request = dns.message.from_wire(data)
+ request = dns.message.from_wire(data, ignore_trailing=ignoreTrailing)
response = cls._getResponse(request)
+
if not response:
continue
sock.close()
@classmethod
- def sendUDPQuery(cls, query, response, useQueue=True, timeout=2.0):
+ def sendUDPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
if useQueue:
cls._toResponderQueue.put(response, True, timeout)
cls._sock.settimeout(timeout)
try:
- cls._sock.send(query.to_wire())
+ if not rawQuery:
+ query = query.to_wire()
+ cls._sock.send(query)
data = cls._sock.recv(4096)
except socket.timeout:
data = None
return (receivedQuery, message)
@classmethod
- def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0):
+ def sendTCPQuery(cls, query, response, useQueue=True, timeout=2.0, rawQuery=False):
if useQueue:
cls._toResponderQueue.put(response, True, timeout)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", cls._dnsDistPort))
try:
- wire = query.to_wire()
+ if not rawQuery:
+ wire = query.to_wire()
+ else:
+ wire = query
+
sock.send(struct.pack("!H", len(wire)))
sock.send(wire)
data = sock.recv(2)
class TestAdvancedAndNot(DNSDistTest):
_config_template = """
- addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(4))
+ addAction(AndRule({NotRule(QTypeRule("A")), TCPRule(false)}), RCodeAction(dnsdist.NOTIMP))
newServer{address="127.0.0.1:%s"}
"""
def testAOverUDPReturnsNotImplementedCanary(self):
class TestAdvancedOr(DNSDistTest):
_config_template = """
- addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(4))
+ addAction(OrRule({QTypeRule("A"), TCPRule(false)}), RCodeAction(dnsdist.NOTIMP))
newServer{address="127.0.0.1:%s"}
"""
def testAAAAOverUDPReturnsNotImplemented(self):
_config_template = """
newServer{address="127.0.0.1:%s"}
- addAction(QClassRule(3), DropAction())
+ addAction(QClassRule(DNSClass.CHAOS), DropAction())
"""
def testAdvancedQClassChaosDrop(self):
"""
"""
name = 'qclasschaos.advanced.tests.powerdns.com.'
query = dns.message.make_query(name, 'TXT', 'CHAOS')
- response = dns.message.make_response(query)
- rrset = dns.rrset.from_text(name,
- 3600,
- dns.rdataclass.CH,
- dns.rdatatype.TXT,
- 'hop')
- response.answer.append(rrset)
- (_, receivedResponse) = self.sendUDPQuery(query, response)
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None)
self.assertEquals(receivedResponse, None)
- (_, receivedResponse) = self.sendTCPQuery(query, response)
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None)
self.assertEquals(receivedResponse, None)
def testAdvancedQClassINAllow(self):
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
+class TestAdvancedOpcode(DNSDistTest):
+
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+ addAction(OpcodeRule(DNSOpcode.Notify), DropAction())
+ """
+ def testAdvancedOpcodeNotifyDrop(self):
+ """
+ Advanced: Drop Opcode NOTIFY
+
+ """
+ name = 'opcodenotify.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.set_opcode(dns.opcode.NOTIFY)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None)
+ self.assertEquals(receivedResponse, None)
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None)
+ self.assertEquals(receivedResponse, None)
+
+ def testAdvancedOpcodeUpdateINAllow(self):
+ """
+ Advanced: Allow Opcode UPDATE
+
+ """
+ name = 'opcodeupdate.advanced.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ query.set_opcode(dns.opcode.UPDATE)
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
class TestAdvancedNonTerminalRule(DNSDistTest):
query = dns.message.make_query(name, 'A', 'IN')
# dnsdist set RA = RD for spoofed responses
query.flags &= ~dns.flags.RD
- expectedQuery = dns.message.make_query(name, 'A', 'IN')
response = dns.message.make_response(query)
rrset = dns.rrset.from_text(name,
_config_template = """
addQPSLimit("qpsnone.advanced.tests.powerdns.com", 100)
- addAction(AllRule(), RCodeAction(5))
+ addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
newServer{address="127.0.0.1:%s"}
"""
_config_template = """
allowed = newNMG()
allowed:addMask("192.0.2.1/32")
- addAction(NotRule(NetmaskGroupRule(allowed)), RCodeAction(5))
+ addAction(NotRule(NetmaskGroupRule(allowed)), RCodeAction(dnsdist.REFUSED))
newServer{address="127.0.0.1:%s"}
"""
(_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
self.assertEquals(receivedResponse, expectedResponse)
+
newServer{address="127.0.0.1:%s"}
truncateTC(true)
addAnyTCRule()
- addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(5))
+ addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(dnsdist.REFUSED))
mySMN = newSuffixMatchNode()
mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
- addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(4))
+ addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(dnsdist.NOTIMP))
addAction(makeRule("drop.test.powerdns.com."), DropAction())
block=newDNSName("powerdns.org.")
function blockFilter(dq)
import Queue
import threading
import socket
+import sys
import time
-import unittest
-import dns
from dnsdisttests import DNSDistTest
class TestCarbon(DNSDistTest):
import unittest
import os
import subprocess
-import sys
import time
class TestCheckConfig(unittest.TestCase):
newServer{address="127.0.0.1:53"}
truncateTC(true)
addAnyTCRule()
- addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(5))
+ addAction(RegexRule("evil[0-9]{4,}\\\\.regex\\\\.tests\\\\.powerdns\\\\.com$"), RCodeAction(dnsdist.REFUSED))
mySMN = newSuffixMatchNode()
mySMN:add(newDNSName("nameAndQtype.tests.powerdns.com."))
- addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(4))
+ addAction(AndRule{SuffixMatchNodeRule(mySMN), QTypeRule("TXT")}, RCodeAction(dnsdist.NOTIMP))
addAction(makeRule("drop.test.powerdns.com."), DropAction())
block=newDNSName("powerdns.org.")
function blockFilter(dq)
#!/usr/bin/env python
-import unittest
import dns
import clientsubnetoption
import cookiesoption
--- /dev/null
+#!/usr/bin/env python
+import copy
+import os
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestRecordsCountOnlyOneAR(DNSDistTest):
+
+ _config_template = """
+ addAction(NotRule(RecordsCountRule(DNSSection.Additional, 1, 1)), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testRecordsCountRefuseEmptyAR(self):
+ """
+ RecordsCount: Refuse arcount == 0
+
+ Send a query to "refuseemptyar.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refuseemptyar.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ def testRecordsCountAllowOneAR(self):
+ """
+ RecordsCount: Allow arcount == 1
+
+ Send a query to "allowonear.recordscount.tests.powerdns.com.",
+ check that we are getting a valid response.
+ """
+ name = 'allowonear.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ response = dns.message.make_response(query)
+ response.answer.append(dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1'))
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testRecordsCountRefuseTwoAR(self):
+ """
+ RecordsCount: Refuse arcount > 1
+
+ Send a query to "refusetwoar.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refusetwoar.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ query.additional.append(dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1'))
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+class TestRecordsCountMoreThanOneLessThanFour(DNSDistTest):
+
+ _config_template = """
+ addAction(RecordsCountRule(DNSSection.Answer, 2, 3), AllowAction())
+ addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testRecordsCountRefuseOneAN(self):
+ """
+ RecordsCount: Refuse ancount == 0
+
+ Send a query to "refusenoan.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refusenoan.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ def testRecordsCountAllowTwoAN(self):
+ """
+ RecordsCount: Allow ancount == 2
+
+ Send a query to "allowtwoan.recordscount.tests.powerdns.com.",
+ check that we are getting a valid response.
+ """
+ name = 'allowtwoan.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ rrset = dns.rrset.from_text_list(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ ['127.0.0.1', '127.0.0.2'])
+ query.answer.append(rrset)
+ response = dns.message.make_response(query)
+ response.answer.append(rrset)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testRecordsCountRefuseFourAN(self):
+ """
+ RecordsCount: Refuse ancount > 3
+
+ Send a query to "refusefouran.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refusefouran.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ rrset = dns.rrset.from_text_list(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ ['127.0.0.1', '127.0.0.2', '127.0.0.3', '127.0.0.4'])
+ query.answer.append(rrset)
+
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+ expectedResponse.answer.append(rrset)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+class TestRecordsCountNothingInNS(DNSDistTest):
+
+ _config_template = """
+ addAction(RecordsCountRule(DNSSection.Authority, 0, 0), AllowAction())
+ addAction(AllRule(), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testRecordsCountRefuseNS(self):
+ """
+ RecordsCount: Refuse nscount != 0
+
+ Send a query to "refusens.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refusens.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.NS,
+ 'ns.tests.powerdns.com.')
+ query.authority.append(rrset)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+ expectedResponse.authority.append(rrset)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+
+ def testRecordsCountAllowEmptyNS(self):
+ """
+ RecordsCount: Allow nscount == 0
+
+ Send a query to "allowns.recordscount.tests.powerdns.com.",
+ check that we are getting a valid response.
+ """
+ name = 'allowns.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ response.answer.append(dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1'))
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+class TestRecordsCountNoOPTInAR(DNSDistTest):
+
+ _config_template = """
+ addAction(NotRule(RecordsTypeCountRule(DNSSection.Additional, dnsdist.OPT, 0, 0)), RCodeAction(dnsdist.REFUSED))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testRecordsCountRefuseOPTinAR(self):
+ """
+ RecordsTypeCount: Refuse OPT in AR
+
+ Send a query to "refuseoptinar.recordscount.tests.powerdns.com.",
+ check that we are getting a REFUSED response.
+ """
+ name = 'refuseoptinar.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, expectedResponse)
+
+ def testRecordsCountAllowNoOPTInAR(self):
+ """
+ RecordsTypeCount: Allow no OPT in AR
+
+ Send a query to "allownooptinar.recordscount.tests.powerdns.com.",
+ check that we are getting a valid response.
+ """
+ name = 'allowwnooptinar.recordscount.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ response.answer.append(dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1'))
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
--- /dev/null
+#!/usr/bin/env python
+import threading
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestTrailing(DNSDistTest):
+
+ # this test suite uses a different responder port
+ # because, contrary to the other ones, its
+ # responders allow trailing data and we don't want
+ # to mix things up.
+ _testServerPort = 5360
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+ addAction(AndRule({QTypeRule(dnsdist.AAAA), TrailingDataRule()}), DropAction())
+ """
+ @classmethod
+ def startResponders(cls):
+ print("Launching responders..")
+
+ cls._UDPResponder = threading.Thread(name='UDP Responder', target=cls.UDPResponder, args=[cls._testServerPort, True])
+ cls._UDPResponder.setDaemon(True)
+ cls._UDPResponder.start()
+ cls._TCPResponder = threading.Thread(name='TCP Responder', target=cls.TCPResponder, args=[cls._testServerPort, True])
+ cls._TCPResponder.setDaemon(True)
+ cls._TCPResponder.start()
+
+ def testTrailingAllowed(self):
+ """
+ Trailing: Allowed
+
+ """
+ name = 'allowed.trailing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '127.0.0.1')
+ response.answer.append(rrset)
+
+ raw = query.to_wire()
+ raw = raw + 'A'* 20
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(raw, response, rawQuery=True)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(raw, response, rawQuery=True)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ def testTrailingDropped(self):
+ """
+ Trailing: dropped
+
+ """
+ name = 'dropped.trailing.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'AAAA', 'IN')
+
+ raw = query.to_wire()
+ raw = raw + 'A'* 20
+
+ (_, receivedResponse) = self.sendUDPQuery(raw, response=None, rawQuery=True)
+ self.assertEquals(receivedResponse, None)
+ (_, receivedResponse) = self.sendTCPQuery(raw, response=None, rawQuery=True)
+ self.assertEquals(receivedResponse, None)