*/
#include "dnsdist-dnsparser.hh"
#include "dnsparser.hh"
+#include "iputils.hh"
namespace dnsdist
{
}
+namespace RecordParsers
+{
+ std::optional<ComboAddress> parseARecord(const std::string_view& packet, const DNSPacketOverlay::Record& record)
+ {
+ if (record.d_type != QType::A || record.d_contentLength != 4) {
+ return {};
+ }
+
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage): length is passed in and used to read data
+ return makeComboAddressFromRaw(4, packet.substr(record.d_contentOffset, record.d_contentOffset + 4).data(), record.d_contentLength);
+ }
+
+ std::optional<ComboAddress> parseAAAARecord(const std::string_view& packet, const DNSPacketOverlay::Record& record)
+ {
+ if (record.d_type != QType::AAAA || record.d_contentLength != 16) {
+ return {};
+ }
+
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage): length is passed in and used to read data
+ return makeComboAddressFromRaw(6, packet.substr(record.d_contentOffset, record.d_contentOffset + 16).data(), record.d_contentLength);
+ }
+
+ std::optional<ComboAddress> parseAddressRecord(const std::string_view& packet, const DNSPacketOverlay::Record& record)
+ {
+ if (record.d_type == QType::A && record.d_contentLength == 4) {
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage): length is passed in and used to read data
+ return makeComboAddressFromRaw(4, packet.substr(record.d_contentOffset, record.d_contentOffset + 4).data(), record.d_contentLength);
+ }
+
+ if (record.d_type == QType::AAAA && record.d_contentLength == 16) {
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage): length is passed in and used to read data
+ return makeComboAddressFromRaw(6, packet.substr(record.d_contentOffset, record.d_contentOffset + 16).data(), record.d_contentLength);
+ }
+
+ return {};
+ }
+
+ std::optional<DNSName> parseCNAMERecord(const std::string_view& packet, const DNSPacketOverlay::Record& record)
+ {
+ if (record.d_type != QType::CNAME) {
+ return {};
+ }
+
+ // NOLINTNEXTLINE(bugprone-suspicious-stringview-data-usage): length is passed in and used to read data
+ return DNSName(packet.data(), record.d_contentOffset + record.d_contentLength, record.d_contentOffset, true);
+ }
+}
+
void setResponseHeadersFromConfig(dnsheader& dnsheader, const ResponseConfig& config)
{
if (config.setAA) {
#pragma once
#include "dnsparser.hh"
+#include "iputils.hh"
namespace dnsdist
{
void restrictDNSPacketTTLs(PacketBuffer& packet, uint32_t minimumValue, uint32_t maximumValue = std::numeric_limits<uint32_t>::max(), const std::unordered_set<QType>& types = {});
}
+namespace RecordParsers
+{
+ std::optional<ComboAddress> parseARecord(const std::string_view& packet, const DNSPacketOverlay::Record& record);
+ std::optional<ComboAddress> parseAAAARecord(const std::string_view& packet, const DNSPacketOverlay::Record& record);
+ std::optional<ComboAddress> parseAddressRecord(const std::string_view& packet, const DNSPacketOverlay::Record& record);
+ std::optional<DNSName> parseCNAMERecord(const std::string_view& packet, const DNSPacketOverlay::Record& record);
+}
+
struct ResponseConfig
{
boost::optional<bool> setAA{boost::none};
luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("contentLength"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_contentLength; });
luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("contentOffset"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_contentOffset; });
+ luaCtx.writeFunction("parseARecord", [](const std::string& packet, const dnsdist::DNSPacketOverlay::Record& record) {
+ return dnsdist::RecordParsers::parseARecord(packet, record);
+ });
+ luaCtx.writeFunction("parseAAAARecord", [](const std::string& packet, const dnsdist::DNSPacketOverlay::Record& record) {
+ return dnsdist::RecordParsers::parseAAAARecord(packet, record);
+ });
+ luaCtx.writeFunction("parseAddressRecord", [](const std::string& packet, const dnsdist::DNSPacketOverlay::Record& record) {
+ return dnsdist::RecordParsers::parseAddressRecord(packet, record);
+ });
+ luaCtx.writeFunction("parseCNAMERecord", [](const std::string& packet, const dnsdist::DNSPacketOverlay::Record& record) {
+ return dnsdist::RecordParsers::parseCNAMERecord(packet, record);
+ });
#endif /* DISABLE_DNSPACKET_BINDINGS */
}
uint16_t dnsdist_ffi_dnspacket_get_record_content_length(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
uint16_t dnsdist_ffi_dnspacket_get_record_content_offset(const dnsdist_ffi_dnspacket_t* packet, size_t idx) __attribute__ ((visibility ("default")));
size_t dnsdist_ffi_dnspacket_get_name_at_offset_raw(const char* packet, size_t packetSize, size_t offset, char* name, size_t nameSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnspacket_parse_a_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnspacket_parse_aaaa_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnspacket_parse_address_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize) __attribute__ ((visibility ("default")));
+bool dnsdist_ffi_dnspacket_parse_cname_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* name, size_t* nameSize) __attribute__ ((visibility ("default")));
void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t*) __attribute__ ((visibility ("default")));
bool dnsdist_ffi_metric_declare(const char* name, size_t nameLen, const char* type, const char* description, const char* customName) __attribute__ ((visibility ("default")));
return 0;
}
+bool dnsdist_ffi_dnspacket_parse_a_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize)
+{
+ if (raw == nullptr || packet == nullptr || addr == nullptr || addrSize == nullptr || idx >= packet->overlay.d_records.size()) {
+ return false;
+ }
+
+ auto record = packet->overlay.d_records.at(idx);
+ if (record.d_type != QType::A || record.d_contentLength != 4) {
+ return false;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): this is a C API
+ memcpy(addr, &raw[record.d_contentOffset], 4);
+ *addrSize = record.d_contentLength;
+
+ return true;
+}
+
+bool dnsdist_ffi_dnspacket_parse_aaaa_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize)
+{
+ if (raw == nullptr || packet == nullptr || addr == nullptr || addrSize == nullptr || idx >= packet->overlay.d_records.size()) {
+ return false;
+ }
+
+ auto record = packet->overlay.d_records.at(idx);
+ if (record.d_type != QType::AAAA || record.d_contentLength != 16) {
+ return false;
+ }
+
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): this is a C API
+ memcpy(addr, &raw[record.d_contentOffset], 16);
+ *addrSize = record.d_contentLength;
+
+ return true;
+}
+
+bool dnsdist_ffi_dnspacket_parse_address_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* addr, size_t* addrSize)
+{
+ if (raw == nullptr || packet == nullptr || addr == nullptr || addrSize == nullptr || idx >= packet->overlay.d_records.size()) {
+ return false;
+ }
+
+ auto record = packet->overlay.d_records.at(idx);
+ if (record.d_type == QType::A && record.d_contentLength == 4) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): this is a C API
+ memcpy(addr, &raw[record.d_contentOffset], 4);
+ *addrSize = record.d_contentLength;
+
+ return true;
+ }
+
+ if (record.d_type == QType::AAAA && record.d_contentLength == 16) {
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic): this is a C API
+ memcpy(addr, &raw[record.d_contentOffset], 16);
+ *addrSize = record.d_contentLength;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool dnsdist_ffi_dnspacket_parse_cname_record(const char* raw, const dnsdist_ffi_dnspacket_t* packet, size_t idx, char* name, size_t* nameSize)
+{
+ if (raw == nullptr || packet == nullptr || name == nullptr || nameSize == nullptr || idx >= packet->overlay.d_records.size()) {
+ return false;
+ }
+
+ auto record = packet->overlay.d_records.at(idx);
+ if (record.d_type != QType::CNAME) {
+ return false;
+ }
+
+ DNSName parsed(raw, record.d_contentOffset + record.d_contentLength, record.d_contentOffset, true);
+ const auto& storage = parsed.getStorage();
+ memcpy(name, storage.data(), storage.size());
+ *nameSize = storage.size();
+
+ return true;
+}
+
void dnsdist_ffi_dnspacket_free(dnsdist_ffi_dnspacket_t* packet)
{
if (packet != nullptr) {
BOOST_AUTO_TEST_CASE(test_Overlay)
{
const DNSName target("powerdns.com.");
+ const DNSName notTheTarget("not-powerdns.com.");
{
PacketBuffer response;
lastOffset = record.d_contentOffset + record.d_contentLength;
}
}
+
+ {
+ /* response with A and AAAA records, using parsers */
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->id = htons(42);
+ pwR.startRecord(target, QType::A, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ ComboAddress v4("192.0.2.1");
+ pwR.xfrCAWithoutPort(4, v4);
+ pwR.commit();
+ pwR.startRecord(target, QType::AAAA, 7200, QClass::IN, DNSResourceRecord::ADDITIONAL);
+ ComboAddress v6("2001:db8::1");
+ pwR.xfrCAWithoutPort(6, v6);
+ pwR.commit();
+ pwR.addOpt(4096, 0, 0);
+ pwR.commit();
+
+ auto packet = std::string_view(reinterpret_cast<const char*>(response.data()), response.size());
+ dnsdist::DNSPacketOverlay overlay(packet);
+ BOOST_CHECK_EQUAL(overlay.d_records[0].d_type, QType::A);
+ BOOST_CHECK(*dnsdist::RecordParsers::parseARecord(packet, overlay.d_records[0]) == v4);
+ BOOST_CHECK(*dnsdist::RecordParsers::parseAddressRecord(packet, overlay.d_records[0]) == v4);
+
+ BOOST_CHECK_EQUAL(overlay.d_records[1].d_type, QType::AAAA);
+ BOOST_CHECK(*dnsdist::RecordParsers::parseAAAARecord(packet, overlay.d_records[1]) == v6);
+ BOOST_CHECK(*dnsdist::RecordParsers::parseAddressRecord(packet, overlay.d_records[1]) == v6);
+ }
+
+ {
+ /* response with CNAME record, using parser */
+ PacketBuffer response;
+ GenericDNSPacketWriter<PacketBuffer> pwR(response, target, QType::A, QClass::IN, 0);
+ pwR.getHeader()->qr = 1;
+ pwR.getHeader()->rd = 1;
+ pwR.getHeader()->ra = 1;
+ pwR.getHeader()->id = htons(42);
+ pwR.startRecord(target, QType::CNAME, 7200, QClass::IN, DNSResourceRecord::ANSWER);
+ pwR.xfrName(notTheTarget);
+ pwR.commit();
+ pwR.addOpt(4096, 0, 0);
+ pwR.commit();
+
+ auto packet = std::string_view(reinterpret_cast<const char*>(response.data()), response.size());
+ dnsdist::DNSPacketOverlay overlay(packet);
+ BOOST_CHECK_EQUAL(overlay.d_records[0].d_type, QType::CNAME);
+ BOOST_CHECK_EQUAL(*dnsdist::RecordParsers::parseCNAMERecord(packet, overlay.d_records[0]), notTheTarget);
+ }
}
BOOST_AUTO_TEST_SUITE_END();
receivedQuery.id = query.id
self.assertEqual(query, receivedQuery)
self.assertEqual(receivedResponse, response)
+
+
+class TestDNSRecordParser(DNSDistTest):
+
+ _verboseMode = True
+ _config_template = """
+ function checkResponsePacket(dq)
+ local packet = dq:getContent()
+ local overlay = newDNSPacketOverlay(packet)
+ local count = overlay:getRecordsCountInSection(DNSSection.Answer)
+ for i = 0, count - 1 do
+ local record = overlay:getRecord(i)
+ local parsedAsA = parseARecord(packet, record)
+ local parsedAsAAAA = parseAAAARecord(packet, record)
+ local parsedAsAddress = parseAddressRecord(packet, record)
+ local parsedAsCNAME = parseCNAMERecord(packet, record)
+ if record.type == DNSQType.A then
+ if parsedAsA:toString() ~= "192.0.2.1" then
+ print(parsedAsA:toString()..".invalid.parsed.a.record.")
+ return DNSResponseAction.ServFail
+ end
+ if parsedAsAddress:toString() ~= "192.0.2.1" then
+ print(parsedAsAddress:toString()..".invalid.parsed.a.record. as address")
+ return DNSResponseAction.ServFail
+ end
+ else
+ if parsedAsA then
+ print("Unexpected A parse success")
+ return DNSResponseAction.ServFail
+ end
+ end
+
+ if record.type == DNSQType.AAAA then
+ if parsedAsAAAA:toString() ~= "ff:db8::ffff" then
+ print(parsedAsAAAA:toString()..".invalid.parsed.aaaa.record.")
+ return DNSResponseAction.ServFail
+ end
+ if parsedAsAddress:toString() ~= "ff:db8::ffff" then
+ print(parsedAsAddress:toString()..".invalid.parsed.aaaa.record. as address")
+ return DNSResponseAction.ServFail
+ end
+ else
+ if parsedAsAAAA then
+ print("Unexpected AAAA parse success")
+ return DNSResponseAction.ServFail
+ end
+ end
+
+ if record.type == DNSQType.CNAME then
+ if parsedAsCNAME:toString() ~= "not-powerdns.com." then
+ print(parsedAsCNAME:toString()..".invalid.parsed.cname.record.")
+ return DNSResponseAction.ServFail
+ end
+ if parsedAsAddress then
+ print("Unexpected address parse success")
+ return DNSResponseAction.ServFail
+ end
+ else
+ if parsedAsCNAME then
+ print("Unexpected CNAME parse success")
+ return DNSResponseAction.ServFail
+ end
+ end
+ end
+ return DNSAction.None
+ end
+
+ addResponseAction(AllRule(), LuaResponseAction(checkResponsePacket))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testQuestionAndResponseWithParsers(self):
+ """
+ DNS Parser: parsers checks
+ """
+ name = 'powerdns.com.'
+ query = dns.message.make_query(name, 'A', 'IN', use_edns=True)
+ response = dns.message.make_response(query)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.A,
+ '192.0.2.1')
+ response.answer.append(rrset)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.AAAA,
+ 'ff:db8::ffff')
+ response.answer.append(rrset)
+ rrset = dns.rrset.from_text(name,
+ 3600,
+ dns.rdataclass.IN,
+ dns.rdatatype.CNAME,
+ 'not-powerdns.com.')
+ response.answer.append(rrset)
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ (receivedQuery, receivedResponse) = sender(query, response)
+ print(receivedResponse)
+ self.assertTrue(receivedQuery)
+ self.assertTrue(receivedResponse)
+ receivedQuery.id = query.id
+ self.assertEqual(query, receivedQuery)
+ self.assertEqual(receivedResponse, response)