-aaaarecord
+aaarecord
aae
aaldering
ababd
dnsnameset
dnsop
dnspacket
+dnsparser
dnspcap
dnsquestion
DNSR
setupLuaConfig(luaCtx, client, configCheck);
setupLuaBindings(luaCtx, client);
setupLuaBindingsDNSCrypt(luaCtx, client);
+ setupLuaBindingsDNSParser(luaCtx);
setupLuaBindingsDNSQuestion(luaCtx);
setupLuaBindingsKVS(luaCtx, client);
setupLuaBindingsNetwork(luaCtx, client);
void setupLuaActions(LuaContext& luaCtx);
void setupLuaBindings(LuaContext& luaCtx, bool client);
void setupLuaBindingsDNSCrypt(LuaContext& luaCtx, bool client);
+void setupLuaBindingsDNSParser(LuaContext& luaCtx);
void setupLuaBindingsDNSQuestion(LuaContext& luaCtx);
void setupLuaBindingsKVS(LuaContext& luaCtx, bool client);
void setupLuaBindingsNetwork(LuaContext& luaCtx, bool client);
dnsdist-lbpolicies.cc dnsdist-lbpolicies.hh \
dnsdist-lua-actions.cc \
dnsdist-lua-bindings-dnscrypt.cc \
+ dnsdist-lua-bindings-dnsparser.cc \
dnsdist-lua-bindings-dnsquestion.cc \
dnsdist-lua-bindings-kvs.cc \
dnsdist-lua-bindings-network.cc \
uint64_t numRecords = ntohs(d_header.ancount) + ntohs(d_header.nscount) + ntohs(d_header.arcount);
d_records.reserve(numRecords);
- try
- {
+ try {
PacketReader reader(pdns_string_view(reinterpret_cast<const char*>(packet.data()), packet.size()));
- for (uint16_t n = 0; n < ntohs(d_header.qdcount) ; ++n) {
+ for (uint16_t n = 0; n < ntohs(d_header.qdcount); ++n) {
reader.xfrName(d_qname);
reader.xfrType(d_qtype);
reader.xfrType(d_qclass);
}
}
}
-
--- /dev/null
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "dnsdist.hh"
+#include "dnsdist-dnsparser.hh"
+#include "dnsdist-lua.hh"
+
+void setupLuaBindingsDNSParser(LuaContext& luaCtx)
+{
+#ifndef DISABLE_DNSPACKET_BINDINGS
+ luaCtx.writeFunction("newDNSPacketOverlay", [](const std::string& packet) {
+ dnsdist::DNSPacketOverlay dpo(packet);
+ return dpo;
+ });
+
+ luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::*)>(std::string("qname"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qname; });
+ luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qtype"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qtype; });
+ luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::*)>(std::string("qclass"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_qclass; });
+ luaCtx.registerMember<dnsheader(dnsdist::DNSPacketOverlay::*)>(std::string("dh"), [](const dnsdist::DNSPacketOverlay& overlay) { return overlay.d_header; });
+
+ luaCtx.registerFunction<uint16_t (dnsdist::DNSPacketOverlay::*)(uint8_t) const>("getRecordsCountInSection", [](const dnsdist::DNSPacketOverlay& overlay, uint8_t section) -> uint16_t {
+ if (section > 3) {
+ return 0;
+ }
+ uint16_t count = 0;
+ for (const auto& record : overlay.d_records) {
+ if (record.d_place == section) {
+ count++;
+ }
+ }
+
+ return count;
+ });
+
+ luaCtx.registerFunction<dnsdist::DNSPacketOverlay::Record (dnsdist::DNSPacketOverlay::*)(size_t) const>("getRecord", [](const dnsdist::DNSPacketOverlay& overlay, size_t idx) {
+ return overlay.d_records.at(idx);
+ });
+
+ luaCtx.registerMember<DNSName(dnsdist::DNSPacketOverlay::Record::*)>(std::string("name"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_name; });
+ luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("type"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_type; });
+ luaCtx.registerMember<uint16_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("class"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_class; });
+ luaCtx.registerMember<uint32_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("ttl"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_ttl; });
+ luaCtx.registerMember<uint8_t(dnsdist::DNSPacketOverlay::Record::*)>(std::string("place"), [](const dnsdist::DNSPacketOverlay::Record& record) { return record.d_place; });
+ 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; });
+
+#endif /* DISABLE_DNSPACKET_BINDINGS */
+}
--- /dev/null
+DNS Parser
+==========
+
+Since 1.8.0, dnsdist contains a limited DNS parser class that can be used to inspect
+the content of DNS queries and responses in Lua.
+
+The first step is to get the content of the DNS payload into a Lua string,
+for example using :meth:`DNSQuestion:getContent`, or :meth:`DNSResponse:getContent`,
+and then to create a :class:`DNSPacketOverlay` object:
+
+.. code-block:: lua
+
+ function dumpPacket(dq)
+ local packet = dq:getContent()
+ local overlay = newDNSPacketOverlay(packet)
+ print(overlay.qname)
+ print(overlay.qtype)
+ print(overlay.qclass)
+ local count = overlay:getRecordsCountInSection(1)
+ print(count)
+ for idx=0, count-1 do
+ local record = overlay:getRecord(idx)
+ print(record.name)
+ print(record.type)
+ print(record.class)
+ print(record.ttl)
+ print(record.place)
+ print(record.contentLength)
+ print(record.contentOffset)
+ end
+ return DNSAction.None
+ end
+
+ addAction(AllRule(), LuaAction(dumpPacket))
+
+
+.. function:: newDNSPacketOverlay(packet) -> DNSPacketOverlay
+
+ .. versionadded:: 1.8.0
+
+ Returns a DNSPacketOverlay
+
+ :param str packet: The DNS payload
+
+.. class:: DNSPacketOverlay
+
+ .. versionadded:: 1.8.0
+
+ The DNSPacketOverlay object has several attributes, all of them read-only:
+
+ .. attribute:: DNSPacketOverlay.qname
+
+ The qname of this packet, as a :ref:`DNSName`.
+
+ .. attribute:: DNSPacketOverlay.qtype
+
+ The type of the query in this packet.
+
+ .. attribute:: DNSPacketOverlay.qclass
+
+ The class of the query in this packet.
+
+ .. attribute:: DNSPacketOverlay.dh
+
+ It also supports the following methods:
+
+ .. method:: DNSPacketOverlay:getRecordsCountInSection(section) -> int
+
+ Returns the number of records in the ANSWER (1), AUTHORITY (2) and
+ ADDITIONAL (3) section of this packet. The number of records in the
+ QUESTION (0) is always set to 0, look at the dnsheader if you need
+ the actual qdcount.
+
+ :param int section: The section, see above
+
+ .. method:: DNSPacketOverlay:getRecord(idx) -> DNSRecord
+
+ Get the record at the requested position. The records in the
+ QUESTION sections are not taken into account, so the first record
+ in the answer section would be at position 0.
+
+ :param int idx: The position of the requested record
+
+
+.. _DNSRecord:
+
+DNSRecord object
+==================
+
+.. class:: DNSRecord
+
+ .. versionadded:: 1.8.0
+
+ This object represents an unparsed DNS record, as returned by the :ref:`DNSPacketOverlay` class. It has several attributes, all of them read-only:
+
+ .. attribute:: DNSRecord.name
+
+ The name of this record, as a :ref:`DNSName`.
+
+ .. attribute:: DNSRecord.type
+
+ The type of this record.
+
+ .. attribute:: DNSRecord.class
+
+ The class of this record.
+
+ .. attribute:: DNSRecord.ttl
+
+ The TTL of this record.
+
+ .. attribute:: DNSRecord.place
+
+ The place (section) of this record.
+
+ .. attribute:: DNSRecord.contentLength
+
+ The length, in bytes, of the rdata content of this record.
+
+ .. attribute:: DNSRecord.contentOffset
+
+ The offset since the beginning of the DNS payload, in bytes, at which the
+ rdata content of this record starts.
dq
ebpf
dnscrypt
+ dnsparser
protobuf
dnstap
carbon
--- /dev/null
+#!/usr/bin/env python
+import unittest
+import dns
+from dnsdisttests import DNSDistTest
+
+class TestDNSParser(DNSDistTest):
+
+ _verboseMode = True
+ _config_template = """
+ function checkQueryPacket(dq)
+ local packet = dq:getContent()
+ if #packet ~= 41 then
+ return DNSAction.Spoof, #packet..".invalid.query.size."
+ end
+
+ local overlay = newDNSPacketOverlay(packet)
+ if overlay.qname:toString() ~= "powerdns.com." then
+ return DNSAction.Spoof, overlay.qname:toString().."invalid.query.qname."
+ end
+ if overlay.qtype ~= DNSQType.A then
+ return DNSAction.Spoof, overlay.qtype..".invalid.query.qtype."
+ end
+ if overlay.qclass ~= DNSClass.IN then
+ return DNSAction.Spoof, overlay.qclass..".invalid.query.qclass."
+ end
+ local count = overlay:getRecordsCountInSection(0)
+ if count ~= 0 then
+ return DNSAction.Spoof, count..".invalid.query.count.in.q."
+ end
+ count = overlay:getRecordsCountInSection(1)
+ if count ~= 0 then
+ return DNSAction.Spoof, count..".invalid.query.count.in.a."
+ end
+ count = overlay:getRecordsCountInSection(2)
+ if count ~= 0 then
+ return DNSAction.Spoof, count..".invalid.query.count.in.auth."
+ end
+ count = overlay:getRecordsCountInSection(3)
+ -- for OPT
+ if count ~= 1 then
+ return DNSAction.Spoof, count..".invalid.query.count.in.add."
+ end
+ return DNSAction.None
+ end
+
+ function checkResponsePacket(dq)
+ local packet = dq:getContent()
+ if #packet ~= 57 then
+ print(#packet..".invalid.size.")
+ return DNSResponseAction.ServFail
+ end
+
+ local overlay = newDNSPacketOverlay(packet)
+ if overlay.qname:toString() ~= "powerdns.com." then
+ print(overlay.qname:toString().."invalid.qname.")
+ return DNSResponseAction.ServFail
+ end
+ if overlay.qtype ~= DNSQType.A then
+ print(overlay.qtype..".invalid.qtype.")
+ return DNSResponseAction.ServFail
+ end
+ if overlay.qclass ~= DNSClass.IN then
+ print(overlay.qclass..".invalid.qclass.")
+ return DNSResponseAction.ServFail
+ end
+ local count = overlay:getRecordsCountInSection(0)
+ if count ~= 0 then
+ print(count..".invalid.count.in.q.")
+ return DNSResponseAction.ServFail
+ end
+ count = overlay:getRecordsCountInSection(1)
+ if count ~= 1 then
+ print(count..".invalid.count.in.a.")
+ return DNSResponseAction.ServFail
+ end
+ count = overlay:getRecordsCountInSection(2)
+ if count ~= 0 then
+ print(count..".invalid.count.in.auth.")
+ return DNSResponseAction.ServFail
+ end
+ count = overlay:getRecordsCountInSection(3)
+ -- for OPT
+ if count ~= 1 then
+ print(count..".invalid.count.in.add.")
+ return DNSResponseAction.ServFail
+ end
+ local record = overlay:getRecord(0)
+ if record.name:toString() ~= "powerdns.com." then
+ print(record.name:toString()..".invalid.name.")
+ return DNSResponseAction.ServFail
+ end
+ if record.type ~= DNSQType.A then
+ print(record.type..".invalid.type.")
+ return DNSResponseAction.ServFail
+ end
+ if record.class ~= DNSClass.IN then
+ print(record.class..".invalid.class.")
+ return DNSResponseAction.ServFail
+ end
+ if record.ttl ~= 3600 then
+ print(record.ttl..".invalid.ttl.")
+ return DNSResponseAction.ServFail
+ end
+ if record.place ~= 1 then
+ print(record.place..".invalid.place.")
+ return DNSResponseAction.ServFail
+ end
+ if record.contentLength ~= 4 then
+ print(record.contentLength..".invalid.contentLength.")
+ return DNSResponseAction.ServFail
+ end
+ if record.contentOffset ~= 42 then
+ print(record.contentOffset..".invalid.contentOffset.")
+ return DNSResponseAction.ServFail
+ end
+ return DNSAction.None
+ end
+
+ addAction(AllRule(), LuaAction(checkQueryPacket))
+ addResponseAction(AllRule(), LuaResponseAction(checkResponsePacket))
+ newServer{address="127.0.0.1:%s"}
+ """
+
+ def testQuestionAndResponse(self):
+ """
+ DNS Parser: basic 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)
+
+ 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)