* `maintenance()`: called every second by dnsdist if defined, call functions below from it
* `clearDynBlocks()`: clear all dynamic blocks
* `showDynBlocks()`: show dynamic blocks in force
- * `addDynBlocks(addresses, message[, seconds])`: block the set of addresses with message `msg`, for `seconds` seconds (10 by default)
+ * `addDynBlocks(addresses, message[, seconds[, action]])`: block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)
* `setDynBlocksAction(DNSAction)`: set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported
* `addBPFFilterDynBlocks(addresses, DynBPFFilter[, seconds])`: block the set of addresses using the supplied BPF Filter, for `seconds` seconds (10 by default)
* `exceedServFails(rate, seconds)`: get set of addresses that exceed `rate` servfails/s over `seconds` seconds
{ "addDNSCryptBind", true, "\"127.0.0.1:8443\", \"provider name\", \"/path/to/resolver.cert\", \"/path/to/resolver.key\", [false], [TCP Fast Open queue size]", "listen to incoming DNSCrypt queries on 127.0.0.1 port 8443, with a provider name of `provider name`, using a resolver certificate and associated key stored respectively in the `resolver.cert` and `resolver.key` files. The fifth optional parameter sets SO_REUSEPORT when available. The last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0" },
{ "addDomainBlock", true, "domain", "block queries within this domain" },
{ "addDomainSpoof", true, "domain, ip[, ip6]", "generate answers for A/AAAA/ANY queries using the ip parameters" },
- { "addDynBlocks", true, "addresses, message[, seconds]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default)" },
+ { "addDynBlocks", true, "addresses, message[, seconds[, action]]", "block the set of addresses with message `msg`, for `seconds` seconds (10 by default), applying `action` (default to the one set with `setDynBlocksAction()`)" },
{ "addLocal", true, "netmask, [true], [false], [TCP Fast Open queue size]", "add to addresses we listen on. Second optional parameter sets TCP or not. Third optional parameter sets SO_REUSEPORT when available. Last parameter sets the TCP Fast Open queue size, enabling TCP Fast Open when available and the value is larger than 0" },
{ "addLuaAction", true, "x, func", "where 'x' is all the combinations from `addPoolRule`, and func is a function with the parameter `dq`, which returns an action to be taken on this packet. Good for rare packets but where you want to do a lot of processing" },
{ "addLuaResponseAction", true, "x, func", "where 'x' is all the combinations from `addPoolRule`, and func is a function with the parameter `dr`, which returns an action to be taken on this response packet. Good for rare packets but where you want to do a lot of processing" },
{ "setACL", true, "{netmask, netmask}", "replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us" },
{ "setAPIWritable", true, "bool, dir", "allow modifications via the API. if `dir` is set, it must be a valid directory where the configuration files will be written by the API" },
{ "setDNSSECPool", true, "pool name", "move queries requesting DNSSEC processing to this pool" },
+ { "setDynBlocksAction", true, "action", "set which action is performed when a query is blocked. Only DNSAction.Drop (the default) and DNSAction.Refused are supported" },
{ "setECSOverride", true, "bool", "whether to override an existing EDNS Client Subnet value in the query" },
{ "setECSSourcePrefixV4", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv4 queries" },
{ "setECSSourcePrefixV6", true, "prefix-length", "the EDNS Client Subnet prefix-length used for IPv6 queries" },
});
g_lua.writeFunction("addDynBlocks",
- [](const map<ComboAddress,int>& m, const std::string& msg, boost::optional<int> seconds) {
+ [](const map<ComboAddress,int>& m, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
setLuaSideEffect();
auto slow = g_dynblockNMG.getCopy();
struct timespec until, now;
else
expired=true;
}
- DynBlock db{msg,until};
+ DynBlock db{msg,until,DNSName(),(action ? *action : DNSAction::Action::None)};
db.blocks=count;
if(!got || expired)
warnlog("Inserting dynamic block for %s for %d seconds: %s", capair.first.toString(), actualSeconds, msg);
});
g_lua.writeFunction("addDynBlockSMT",
- [](const vector<pair<unsigned int, string> >&names, const std::string& msg, boost::optional<int> seconds) {
+ [](const vector<pair<unsigned int, string> >&names, const std::string& msg, boost::optional<int> seconds, boost::optional<DNSAction::Action> action) {
setLuaSideEffect();
auto slow = g_dynblockSMT.getCopy();
struct timespec until, now;
expired=true;
}
- DynBlock db{msg,until,domain};
+ DynBlock db{msg,until,domain,(action ? *action : DNSAction::Action::None)};
db.blocks=count;
if(!got || expired)
warnlog("Inserting dynamic block for %s for %d seconds: %s", domain, actualSeconds, msg);
if(now < got->second.until) {
g_stats.dynBlocked++;
got->second.blocks++;
- if (g_dynBlockAction == DNSAction::Action::Refused) {
+ DNSAction::Action action = got->second.action;
+ if (action == DNSAction::Action::None) {
+ action = g_dynBlockAction;
+ }
+ if (action == DNSAction::Action::Refused) {
vinfolog("Query from %s refused because of dynamic block", dq.remote->toStringWithPort());
dq.dh->rcode = RCode::Refused;
dq.dh->qr=true;
if(now < got->until) {
g_stats.dynBlocked++;
got->blocks++;
- if (g_dynBlockAction == DNSAction::Action::Refused) {
+ DNSAction::Action action = got->action;
+ if (action == DNSAction::Action::None) {
+ action = g_dynBlockAction;
+ }
+ if (action == DNSAction::Action::Refused) {
vinfolog("Query from %s for %s refused because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString());
dq.dh->rcode = RCode::Refused;
dq.dh->qr=true;
void* carbonDumpThread();
uint64_t uptimeOfProcess(const std::string& str);
+extern uint16_t g_ECSSourcePrefixV4;
+extern uint16_t g_ECSSourcePrefixV6;
+extern bool g_ECSOverride;
+
+struct DNSQuestion
+{
+ DNSQuestion(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp): qname(name), qtype(type), qclass(class_), local(lc), remote(rem), dh(header), size(bufferSize), len(queryLen), ecsPrefixLength(rem->sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), tcp(isTcp), ecsOverride(g_ECSOverride) { }
+
+#ifdef HAVE_PROTOBUF
+ boost::uuids::uuid uniqueId;
+#endif
+ const DNSName* qname;
+ const uint16_t qtype;
+ const uint16_t qclass;
+ const ComboAddress* local;
+ const ComboAddress* remote;
+ struct dnsheader* dh;
+ size_t size;
+ uint16_t len;
+ uint16_t ecsPrefixLength;
+ const bool tcp;
+ bool skipCache{false};
+ bool ecsOverride;
+ bool useECS{true};
+};
+
+struct DNSResponse : DNSQuestion
+{
+ DNSResponse(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp, const struct timespec* queryTime_): DNSQuestion(name, type, class_, lc, rem, header, bufferSize, queryLen, isTcp), queryTime(queryTime_) { }
+
+ const struct timespec* queryTime;
+};
+
+/* so what could you do:
+ drop,
+ fake up nxdomain,
+ provide actual answer,
+ allow & and stop processing,
+ continue processing,
+ modify header: (servfail|refused|notimp), set TC=1,
+ send to pool */
+
+class DNSAction
+{
+public:
+ enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, None};
+ virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
+ virtual string toString() const = 0;
+ virtual std::unordered_map<string, double> getStats() const
+ {
+ return {{}};
+ }
+};
+
+class DNSResponseAction
+{
+public:
+ enum class Action { Allow, Delay, Drop, HeaderModify, None };
+ virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
+ virtual string toString() const = 0;
+};
+
struct DynBlock
{
DynBlock& operator=(const DynBlock& rhs)
reason=rhs.reason;
until=rhs.until;
domain=rhs.domain;
+ action=rhs.action;
blocks.store(rhs.blocks);
return *this;
}
string reason;
struct timespec until;
DNSName domain;
+ DNSAction::Action action;
mutable std::atomic<unsigned int> blocks;
};
};
using servers_t =vector<std::shared_ptr<DownstreamState>>;
-extern uint16_t g_ECSSourcePrefixV4;
-extern uint16_t g_ECSSourcePrefixV6;
-extern bool g_ECSOverride;
-
-struct DNSQuestion
-{
- DNSQuestion(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp): qname(name), qtype(type), qclass(class_), local(lc), remote(rem), dh(header), size(bufferSize), len(queryLen), ecsPrefixLength(rem->sin4.sin_family == AF_INET ? g_ECSSourcePrefixV4 : g_ECSSourcePrefixV6), tcp(isTcp), ecsOverride(g_ECSOverride) { }
-
-#ifdef HAVE_PROTOBUF
- boost::uuids::uuid uniqueId;
-#endif
- const DNSName* qname;
- const uint16_t qtype;
- const uint16_t qclass;
- const ComboAddress* local;
- const ComboAddress* remote;
- struct dnsheader* dh;
- size_t size;
- uint16_t len;
- uint16_t ecsPrefixLength;
- const bool tcp;
- bool skipCache{false};
- bool ecsOverride;
- bool useECS{true};
-};
-
-struct DNSResponse : DNSQuestion
-{
- DNSResponse(const DNSName* name, uint16_t type, uint16_t class_, const ComboAddress* lc, const ComboAddress* rem, struct dnsheader* header, size_t bufferSize, uint16_t queryLen, bool isTcp, const struct timespec* queryTime_): DNSQuestion(name, type, class_, lc, rem, header, bufferSize, queryLen, isTcp), queryTime(queryTime_) { }
-
- const struct timespec* queryTime;
-};
-
typedef std::function<bool(const DNSQuestion*)> blockfilter_t;
template <class T> using NumberedVector = std::vector<std::pair<unsigned int, T> >;
mutable std::atomic<uint64_t> d_matches{0};
};
-/* so what could you do:
- drop,
- fake up nxdomain,
- provide actual answer,
- allow & and stop processing,
- continue processing,
- modify header: (servfail|refused|notimp), set TC=1,
- send to pool */
-
-class DNSAction
-{
-public:
- enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, None};
- virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
- virtual string toString() const = 0;
- virtual std::unordered_map<string, double> getStats() const
- {
- return {{}};
- }
-};
-
-class DNSResponseAction
-{
-public:
- enum class Action { Allow, Delay, Drop, HeaderModify, None };
- virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
- virtual string toString() const = 0;
-};
-
using NumberedServerVector = NumberedVector<shared_ptr<DownstreamState>>;
typedef std::function<shared_ptr<DownstreamState>(const NumberedServerVector& servers, const DNSQuestion*)> policyfunc_t;
sock.connect(("127.0.0.1", cls._consolePort))
sock.send(ourNonce)
theirNonce = sock.recv(len(ourNonce))
+ if len(theirNonce) != len(ourNonce):
+ print("Received a nonce of size %, expecting %, console command will not be sent!" % (len(theirNonce), len(ourNonce)))
+ return None
halfNonceSize = len(ourNonce) / 2
readingNonce = ourNonce[0:halfNonceSize] + theirNonce[halfNonceSize:]
writingNonce = theirNonce[0:halfNonceSize] + ourNonce[halfNonceSize:]
-
msg = cls._encryptConsole(command, writingNonce)
sock.send(struct.pack("!I", len(msg)))
sock.send(msg)
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
+class TestDynBlockQPSActionRefused(DNSDistTest):
+
+ _dynBlockQPS = 10
+ _dynBlockPeriod = 2
+ _dynBlockDuration = 5
+ _config_params = ['_dynBlockQPS', '_dynBlockPeriod', '_dynBlockDuration', '_testServerPort']
+ _config_template = """
+ function maintenance()
+ addDynBlocks(exceedQRate(%d, %d), "Exceeded query rate", %d, DNSAction.Refused)
+ end
+ setDynBlocksAction(DNSAction.Drop)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks: QRate refused (action)
+ """
+ name = 'qrateactionrefused.dynblocks.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN')
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 60,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ refusedResponse = dns.message.make_response(query)
+ refusedResponse.set_rcode(dns.rcode.REFUSED)
+
+ allowed = 0
+ sent = 0
+ for _ in xrange((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+ allowed = allowed + 1
+ else:
+ self.assertEquals(receivedResponse, refusedResponse)
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should now be 'refused' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, refusedResponse)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
+ allowed = 0
+ sent = 0
+ # again, over TCP this time
+ for _ in xrange((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ sent = sent + 1
+ if receivedQuery:
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+ allowed = allowed + 1
+ else:
+ self.assertEquals(receivedResponse, refusedResponse)
+ # the query has not reached the responder,
+ # let's clear the response queue
+ self.clearToResponderQueue()
+
+ # we might be already blocked, but we should have been able to send
+ # at least self._dynBlockQPS queries
+ self.assertGreaterEqual(allowed, self._dynBlockQPS)
+
+ if allowed == sent:
+ # wait for the maintenance function to run
+ time.sleep(2)
+
+ # we should now be 'refused' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, refusedResponse)
+
+ # wait until we are not blocked anymore
+ time.sleep(self._dynBlockDuration + self._dynBlockPeriod)
+
+ # this one should succeed
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(response, receivedResponse)
+
class TestDynBlockServFails(DNSDistTest):
_dynBlockQPS = 10