To be able to truncate already existing answers.
{ "TagRule", true, "name [, value]", "matches if the tag named 'name' is present, with the given 'value' matching if any" },
{ "TCAction", true, "", "create answer to query with TC and RD bits set, to move to TCP" },
{ "TCPRule", true, "[tcp]", "Matches question received over TCP if tcp is true, over UDP otherwise" },
+ { "TCResponseAction", true, "", "truncate a response" },
{ "TeeAction", true, "remote [, addECS [, local]]", "send copy of query to remote, optionally adding ECS info, optionally set local address" },
{ "testCrypto", true, "", "test of the crypto all works" },
{ "TimedIPSetRule", true, "", "Create a rule which matches a set of IP addresses which expire"},
}
};
+class TCResponseAction : public DNSResponseAction
+{
+public:
+ DNSResponseAction::Action operator()(DNSResponse* dnsResponse, std::string* ruleresult) const override
+ {
+ return Action::Truncate;
+ }
+ std::string toString() const override
+ {
+ return "tc=1 answer";
+ }
+};
+
class LuaAction : public DNSAction
{
public:
return std::shared_ptr<DNSAction>(new TCAction);
});
+ luaCtx.writeFunction("TCResponseAction", []() {
+ return std::shared_ptr<DNSResponseAction>(new TCResponseAction);
+ });
+
luaCtx.writeFunction("SetDisableValidationAction", []() {
return std::shared_ptr<DNSAction>(new SetDisableValidationAction);
});
{"Drop", (int)DNSResponseAction::Action::Drop },
{"HeaderModify", (int)DNSResponseAction::Action::HeaderModify },
{"ServFail", (int)DNSResponseAction::Action::ServFail },
+ {"Truncate", (int)DNSResponseAction::Action::Truncate },
{"None", (int)DNSResponseAction::Action::None }
});
});
return true;
break;
+ case DNSResponseAction::Action::Truncate:
+ if (!dr.overTCP()) {
+ dnsdist::PacketMangling::editDNSHeaderFromPacket(dr.getMutableData(), [](dnsheader& header) {
+ header.tc = true;
+ header.qr = true;
+ return true;
+ });
+ truncateTC(dr.getMutableData(), dr.getMaximumSize(), dr.ids.qname.wirelength());
+ ++dnsdist::metrics::g_stats.ruleTruncated;
+ return true;
+ }
+ break;
/* non-terminal actions follow */
case DNSResponseAction::Action::Delay:
pdns::checked_stoi_into(dr.ids.delayMsec, ruleresult); // sorry
class DNSResponseAction
{
public:
- enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, None };
+ enum class Action : uint8_t { Allow, Delay, Drop, HeaderModify, ServFail, Truncate, None };
virtual Action operator()(DNSResponse*, string* ruleresult) const =0;
virtual ~DNSResponseAction()
{
DNSQuestion* dq{nullptr};
ComboAddress maskedRemote;
std::string trailingData;
- boost::optional<std::string> result{boost::none};
- boost::optional<std::string> httpPath{boost::none};
- boost::optional<std::string> httpQueryString{boost::none};
- boost::optional<std::string> httpHost{boost::none};
- boost::optional<std::string> httpScheme{boost::none};
+ std::optional<std::string> result{std::nullopt};
+ std::optional<std::string> httpPath{std::nullopt};
+ std::optional<std::string> httpQueryString{std::nullopt};
+ std::optional<std::string> httpHost{std::nullopt};
+ std::optional<std::string> httpScheme{std::nullopt};
std::unique_ptr<std::vector<dnsdist_ffi_ednsoption_t>> ednsOptionsVect;
std::unique_ptr<std::vector<dnsdist_ffi_http_header_t>> httpHeadersVect;
std::unique_ptr<std::vector<dnsdist_ffi_tag_t>> tagsVect;
}
DNSResponse* dr{nullptr};
- boost::optional<std::string> result{boost::none};
+ std::optional<std::string> result{std::nullopt};
};
// dnsdist_ffi_server_t is a lightuserdata
DNSResponseAction
-----------------
+.. versionchanged:: 1.8.0
+ The ``DNSResponseAction.Truncate`` value was added.
+
These constants represent an Action that can be returned from :func:`LuaResponseAction` functions.
* ``DNSResponseAction.Allow``: let the response pass, skipping other rules
* ``DNSResponseAction.HeaderModify``: indicate that the query has been turned into a response
* ``DNSResponseAction.None``: continue to the next rule
* ``DNSResponseAction.ServFail``: return a response with a ServFail rcode
+ * ``DNSResponseAction.Truncate``: truncate the response
Before 1.7.0 this action was performed even when the query had been received over TCP, which required the use of :func:`TCPRule` to
prevent the TC bit from being set over TCP transports.
+.. function:: TCResponseAction()
+
+ .. versionadded:: 1.9.0
+
+ Truncate an existing answer, to force the client to TCP.
+
.. function:: TeeAction(remote[, addECS[, local [, addProxyProtocol]]])
.. versionchanged:: 1.8.0
dns.rdatatype.A,
'192.0.2.3')
expectedResponse.answer.append(rrset)
+ self.assertEqual(len(query.to_wire()), self._payloadSize)
for method in ("sendUDPQuery", "sendTCPQuery"):
sender = getattr(self, method)
(_, receivedResponse) = sender(query, response=None, useQueue=False)
self.assertTrue(receivedResponse)
self.assertEqual(receivedResponse, expectedResponse)
+
+class TestTruncateOversizedResponse(DNSDistTest):
+
+ _payloadSize = 512
+ _config_template = """
+ addResponseAction(PayloadSizeRule("greater", %d), TCResponseAction())
+ newServer{address="127.0.0.1:%d"}
+ """
+ _config_params = ['_payloadSize', '_testServerPort']
+
+ def testTruncateOversizedResponse(self):
+ """
+ Size: Truncate oversized response
+ """
+ name = 'truncate-oversized.size.tests.powerdns.com.'
+ query = dns.message.make_query(name, 'TXT', 'IN')
+ query.flags &= ~dns.flags.RD
+ expectedResponse = dns.message.make_response(query)
+ expectedResponse.flags |= dns.flags.TC
+
+ backendResponse = dns.message.make_response(query)
+ content = ''
+ for i in range(2):
+ if len(content) > 0:
+ content = content + ' '
+ content = content + 'A' * 255
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.TXT,
+ content)
+ backendResponse.answer.append(rrset)
+ self.assertGreater(len(backendResponse.to_wire()), self._payloadSize)
+
+ (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response=backendResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(receivedResponse, expectedResponse)
+
+ (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response=backendResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ self.assertEqual(receivedResponse, backendResponse)