* DNSAction.Pool: use the specified pool to forward this query
* DNSAction.Refused: return a response with a Refused rcode
* DNSAction.Spoof: spoof the response using the supplied IPv4 (A), IPv6 (AAAA) or string (CNAME) value
+ * DNSAction.Truncate: return a response with TC=1
The same feature exists to hand off some responses for Lua inspection, using `addLuaResponseAction(x, func)`.
* `clearDynBlocks()`: clear all dynamic blocks
* `showDynBlocks()`: show dynamic blocks in force
* `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
+ * `setDynBlocksAction(DNSAction)`: set which action is performed when a query is blocked. Only DNSAction.Drop (the default), DNSAction.Refused and DNSAction.Truncate 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
* `exceedNXDOMAINs(rate, seconds)`: get set of addresses that exceed `rate` NXDOMAIN/s over `seconds` seconds
{"HeaderModify", (int)DNSAction::Action::HeaderModify},
{"Pool", (int)DNSAction::Action::Pool},
{"None",(int)DNSAction::Action::None},
- {"Delay", (int)DNSAction::Action::Delay}}
- );
+ {"Delay", (int)DNSAction::Action::Delay},
+ {"Truncate", (int)DNSAction::Action::Truncate}
+ });
g_lua.writeVariable("DNSResponseAction", std::unordered_map<string,int>{
{"Allow", (int)DNSResponseAction::Action::Allow },
g_lua.writeFunction("setDynBlocksAction", [](DNSAction::Action action) {
if (!g_configurationDone) {
- if (action == DNSAction::Action::Drop || action == DNSAction::Action::Refused) {
+ if (action == DNSAction::Action::Drop || action == DNSAction::Action::Refused || action == DNSAction::Action::Truncate) {
g_dynBlockAction = action;
}
else {
- errlog("Dynamic blocks action can only be Drop or Refused!");
- g_outputBuffer="Dynamic blocks action can only be Drop or Refused!\n";
+ errlog("Dynamic blocks action can only be Drop, Refused or Truncate!");
+ g_outputBuffer="Dynamic blocks action can only be Drop, Refused or Truncate!\n";
}
} else {
g_outputBuffer="Dynamic blocks action cannot be altered at runtime!\n";
dq.dh->qr=true;
return true;
}
+ else if (action == DNSAction::Action::Truncate && !dq.tcp) {
+ vinfolog("Query from %s truncated because of dynamic block", dq.remote->toStringWithPort());
+ dq.dh->tc = true;
+ dq.dh->qr = true;
+ return true;
+ }
else {
vinfolog("Query from %s dropped because of dynamic block", dq.remote->toStringWithPort());
return false;
dq.dh->qr=true;
return true;
}
+ else if (action == DNSAction::Action::Truncate && !dq.tcp) {
+ vinfolog("Query from %s for %s truncated because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString());
+ dq.dh->tc = true;
+ dq.dh->qr = true;
+ return true;
+ }
else {
vinfolog("Query from %s for %s dropped because of dynamic block", dq.remote->toStringWithPort(), dq.qname->toString());
return false;
spoofResponseFromString(dq, ruleresult);
return true;
break;
+ case DNSAction::Action::Truncate:
+ dq.dh->tc = true;
+ dq.dh->qr = true;
+ return true;
+ break;
case DNSAction::Action::HeaderModify:
return true;
break;
class DNSAction
{
public:
- enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, None};
+ enum class Action { Drop, Nxdomain, Refused, Spoof, Allow, HeaderModify, Pool, Delay, Truncate, None};
virtual Action operator()(DNSQuestion*, string* ruleresult) const =0;
virtual string toString() const = 0;
virtual std::unordered_map<string, double> getStats() const
public:
DNSAction::Action operator()(DNSQuestion* dq, string* ruleresult) const override
{
- dq->dh->tc = true;
- dq->dh->qr = true; // for good measure
- return Action::HeaderModify;
+ return Action::Truncate;
}
string toString() const override
{
(_, receivedResponse) = self.sendUDPQuery(queryWithDO, response=None, useQueue=False)
self.assertTrue(receivedResponse)
doResponse.id = receivedResponse.id
- print(doResponse)
- print(receivedResponse)
self.assertEquals(receivedResponse, doResponse)
(_, receivedResponse) = self.sendTCPQuery(queryWithDO, response=None, useQueue=False)
doResponse.id = receivedResponse.id
self.assertEquals(receivedResponse, doResponse)
+class TestAdvancedLuaRefused(DNSDistTest):
+
+ _config_template = """
+ function refuse(dq)
+ return DNSAction.Refused, ""
+ end
+ addLuaAction(AllRule(), refuse)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testRefusedViaLua(self):
+ """
+ Advanced: Refused via Lua
+ """
+ name = 'refused.advanced.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.AAAA,
+ '::1')
+ response.answer.append(rrset)
+ refusedResponse = dns.message.make_response(query)
+ refusedResponse.set_rcode(dns.rcode.REFUSED)
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ refusedResponse.id = receivedResponse.id
+ self.assertEquals(receivedResponse, refusedResponse)
+
+ (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ refusedResponse.id = receivedResponse.id
+ self.assertEquals(receivedResponse, refusedResponse)
+
+class TestAdvancedLuaTruncated(DNSDistTest):
+
+ _config_template = """
+ function trunc(dq)
+ if not dq.tcp then
+ return DNSAction.Truncate, ""
+ end
+ return DNSAction.None, ""
+ end
+ addLuaAction(AllRule(), trunc)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testTCViaLua(self):
+ """
+ Advanced: TC via Lua
+ """
+ name = 'tc.advanced.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.AAAA,
+ '::1')
+ response.answer.append(rrset)
+
+ truncatedResponse = dns.message.make_response(query)
+ truncatedResponse.flags |= dns.flags.TC
+
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertTrue(receivedResponse)
+ truncatedResponse.id = receivedResponse.id
+ self.assertEquals(receivedResponse, truncatedResponse)
+
+ # no truncation over TCP
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+
class TestStatNodeRespRingSince(DNSDistTest):
_consoleKey = DNSDistTest.generateConsoleKey()
self.assertEquals(query, receivedQuery)
self.assertEquals(response, receivedResponse)
+class TestDynBlockQPSActionTruncated(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.Truncate)
+ end
+ setDynBlocksAction(DNSAction.Drop)
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testDynBlocksQRate(self):
+ """
+ Dyn Blocks: QRate truncated (action)
+ """
+ name = 'qrateactiontruncated.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)
+ truncatedResponse = dns.message.make_response(query)
+ truncatedResponse.flags |= dns.flags.TC
+
+ 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, truncatedResponse)
+ # 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 'truncated' for up to self._dynBlockDuration + self._dynBlockPeriod
+ (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False)
+ self.assertEquals(receivedResponse, truncatedResponse)
+
+ # 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, we should never get truncated!
+ for _ in xrange((self._dynBlockQPS * self._dynBlockPeriod) + 1):
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response)
+ sent = sent + 1
+ self.assertEquals(query, receivedQuery)
+ self.assertEquals(receivedResponse, response)
+ receivedQuery.id = query.id
+ allowed = allowed + 1
+
+ self.assertEquals(allowed, sent)
+
class TestDynBlockServFails(DNSDistTest):
_dynBlockQPS = 10