return true
end
+ local tm = os.time()
+ if dq.queryTime.tv_sec < tm - 1 or dq.queryTime.tv_sec > tm + 1 then
+ pdnslog("queryTime is wrong")
+ dq.rcode = pdns.REFUSED
+ return true
+ end
+
if dq.qtype == pdns.A then
dq:addAnswer(pdns.A, '192.0.2.1')
elseif dq.qtype == pdns.AAAA then
answer = dns.rrset.from_text('postresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.1')
response.answer.append(answer)
+ elif request.question[0].name == dns.name.from_text('preresolve.luahooks.example.'):
+ answer = dns.rrset.from_text('preresolve.luahooks.example.', 3600, dns.rdataclass.IN, 'A', '192.0.2.2')
+ response.answer.append(answer)
+
self.transport.write(response.to_wire(), address)
class LuaHooksRecursorTest(RecursorTest):
return true
end
+ function preresolve(dq)
+ -- test both preresolve hooking and udpQueryResponse
+ if dq.qtype == pdns.A and dq.qname == newDN("preresolve.luahooks.example.") then
+ dq.followupFunction = "udpQueryResponse"
+ dq.udpCallback = "gotUdpResponse"
+ dq.udpQueryDest = newCA("%s.23:53")
+ -- synthesize query, responder should answer with regular answer
+ dq.udpQuery = "\\254\\255\\001\\000\\000\\001\\000\\000\\000\\000\\000\\000\\npreresolve\\008luahooks\\007example\\000\\000\\001\\000\\001"
+ return true
+ end
+ return false
+ end
+
+ function gotUdpResponse(dq)
+ dq.followupFunction = ""
+ if string.len(dq.udpAnswer) == 61 then
+ dq:addAnswer(pdns.A, "192.0.2.2")
+ return true;
+ end
+ dq:addAnswer(pdns.A, "0.0.0.0")
+ return true
+ end
+
function nodata(dq)
if dq.qtype == pdns.AAAA and dq.qname == newDN("nodata.luahooks.example.") then
dq:addAnswer(pdns.AAAA, "2001:DB8::1")
return false
end
- """ % (os.environ['PREFIX'], os.environ['PREFIX'])
+ """ % (os.environ['PREFIX'], os.environ['PREFIX'], os.environ['PREFIX'])
@classmethod
def startResponders(cls):
self.assertRRsetInAnswer(res, expected)
self.assertEqual(res.answer[0].ttl, 1)
+ def testPreResolve(self):
+ expected = dns.rrset.from_text('preresolve.luahooks.example.', 1, dns.rdataclass.IN, 'A', '192.0.2.2')
+ query = dns.message.make_query('preresolve.luahooks.example.', 'A', 'IN')
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ res = sender(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertRRsetInAnswer(res, expected)
+
def testIPFilterHeader(self):
query = dns.message.make_query('ipfilter.luahooks.example.', 'A', 'IN')
query.flags |= dns.flags.AD
function preresolve (dq)
dq.rcode = pdns.NOERROR
dq:addAnswer(pdns.TXT, pdnsrandom())
+ dq.variable = true
return true
end
"""
"""Tests that we have access to the validation states from Lua"""
_confdir = 'validation-states-from-lua'
- _config_template_default = """
+ _config_template = """
dnssec=validate
-daemon=no
-trace=yes
-packetcache-ttl=0
-packetcache-servfail-ttl=0
-max-cache-ttl=15
-threads=1
-loglevel=9
-disable-syslog=yes
-log-common-errors=yes
"""
_roothints = None
_lua_config_file = """
"""
- _config_template = """
- """
+
_lua_dns_script_file = """
function postresolve (dq)
if pdns.validationstates.Indeterminate == nil or
self.assertEqual(len(res.authority), 0)
class PolicyEventFilterOnFollowUpTest(RecursorTest):
- """Tests the interaction between RPZ and followup queries (dns64, folliwCNAME)
+ """Tests the interaction between RPZ and followup queries (dns64, followCNAME)
"""
_confdir = 'policyeventfilter-followup'
self.assertEqual(len(res.answer), 2)
self.assertEqual(len(res.authority), 0)
self.assertResponseMatches(query, expected, res)
+
+class PolicyEventFilterOnFollowUpWithNativeDNS64Test(RecursorTest):
+ """Tests the interaction between followup queries and native dns64
+ """
+
+ _confdir = 'policyeventfilter-followup-dns64'
+ _config_template = """
+ dns64-prefix=1234::/96
+ """
+ _lua_config_file = """
+ """
+ _lua_dns_script_file = """
+ function preresolve(dq)
+ dq:addAnswer(pdns.CNAME, "cname.secure.example.")
+ dq.followupFunction="followCNAMERecords"
+ dq.rcode = pdns.NOERROR
+ return true
+ end
+
+ """
+
+ def testAAAA(self):
+ expected = [
+ dns.rrset.from_text('mx1.secure.example.', 15, dns.rdataclass.IN, 'CNAME', 'cname.secure.example.'),
+ dns.rrset.from_text('cname.secure.example.', 15, dns.rdataclass.IN, 'CNAME', ' host1.secure.example.'),
+ dns.rrset.from_text('host1.secure.example.', 15, dns.rdataclass.IN, 'AAAA', '1234::c000:202')
+ ]
+ query = dns.message.make_query('mx1.secure.example', 'AAAA')
+
+ for method in ("sendUDPQuery", "sendTCPQuery"):
+ sender = getattr(self, method)
+ res = sender(query)
+ print(res)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 3)
+ self.assertEqual(len(res.authority), 0)
+ self.assertResponseMatches(query, expected, res)
+
+class LuaPostResolveFFITest(RecursorTest):
+ """Tests postresolve_ffi interface"""
+
+ _confdir = 'LuaPostResolveFFITest'
+ _config_template = """
+ """
+ _lua_dns_script_file = """
+local ffi = require("ffi")
+
+ffi.cdef[[
+ typedef struct pdns_postresolve_ffi_handle pdns_postresolve_ffi_handle_t;
+
+ typedef enum
+ {
+ pdns_record_place_answer = 1,
+ pdns_record_place_authority = 2,
+ pdns_record_place_additional = 3
+ } pdns_record_place_t;
+
+ typedef enum
+ {
+ pdns_policy_kind_noaction = 0,
+ pdns_policy_kind_drop = 1,
+ pdns_policy_kind_nxdomain = 2,
+ pdns_policy_kind_nodata = 3,
+ pdns_policy_kind_truncate = 4,
+ pdns_policy_kind_custom = 5
+ } pdns_policy_kind_t;
+
+ typedef struct pdns_ffi_record {
+ const char* name;
+ size_t name_len;
+ const char* content;
+ size_t content_len;
+ uint32_t ttl;
+ pdns_record_place_t place;
+ uint16_t type;
+ } pdns_ffi_record_t;
+
+ const char* pdns_postresolve_ffi_handle_get_qname(pdns_postresolve_ffi_handle_t* ref);
+ const char* pdns_postresolve_ffi_handle_get_qname_raw(pdns_postresolve_ffi_handle_t* ref, const char** name, size_t* len);
+ uint16_t pdns_postresolve_ffi_handle_get_qtype(const pdns_postresolve_ffi_handle_t* ref);
+ uint16_t pdns_postresolve_ffi_handle_get_rcode(const pdns_postresolve_ffi_handle_t* ref);
+ void pdns_postresolve_ffi_handle_set_rcode(const pdns_postresolve_ffi_handle_t* ref, uint16_t rcode);
+ void pdns_postresolve_ffi_handle_set_appliedpolicy_kind(pdns_postresolve_ffi_handle_t* ref, pdns_policy_kind_t kind);
+ bool pdns_postresolve_ffi_handle_get_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, pdns_ffi_record_t *record, bool raw);
+ bool pdns_postresolve_ffi_handle_set_record(pdns_postresolve_ffi_handle_t* ref, unsigned int i, const char* content, size_t contentLen, bool raw);
+ void pdns_postresolve_ffi_handle_clear_records(pdns_postresolve_ffi_handle_t* ref);
+ bool pdns_postresolve_ffi_handle_add_record(pdns_postresolve_ffi_handle_t* ref, const char* name, uint16_t type, uint32_t ttl, const char* content, size_t contentLen, pdns_record_place_t place, bool raw);
+ const char* pdns_postresolve_ffi_handle_get_authip(pdns_postresolve_ffi_handle_t* ref);
+ void pdns_postresolve_ffi_handle_get_authip_raw(pdns_postresolve_ffi_handle_t* ref, const void** addr, size_t* addrSize);
+]]
+
+
+function tohex(str)
+ return (str:gsub('.', function (c) return string.format('%02X', string.byte(c)) end))
+end
+function toA(str)
+ return (str:gsub('.', function (c) return string.format('%d.', string.byte(c)) end))
+end
+
+function postresolve_ffi(ref)
+ local qname = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_qname(ref))
+ local qtype = ffi.C.pdns_postresolve_ffi_handle_get_qtype(ref)
+
+ if qname == "example" and qtype == pdns.SOA
+ then
+ local addr = ffi.string(ffi.C.pdns_postresolve_ffi_handle_get_authip(ref))
+ if string.sub(addr, -3) ~= ".10" and string.sub(addr, -3) ~= ".18"
+ then
+ -- signal error by clearing all
+ ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
+ end
+ local qaddr = ffi.new("const char *[1]")
+ local qlen = ffi.new("size_t [1]")
+ ffi.C.pdns_postresolve_ffi_handle_get_qname_raw(ref, qaddr, qlen)
+ local q = ffi.string(qaddr[0], qlen[0])
+ if tohex(q) ~= "076578616D706C6500"
+ then
+ -- pdnslog("Error "..tohex(q))
+ -- signal error by clearing all
+ ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
+ end
+ -- as a bonus check from which auth the data came
+ local addr = ffi.new("const void *[1]")
+ local len = ffi.new("size_t [1]")
+ ffi.C.pdns_postresolve_ffi_handle_get_authip_raw(ref, addr, len)
+ local a = ffi.string(addr[0], len[0])
+ if string.byte(a, 4) ~= 10 and string.byte(a,4) ~= 18
+ then
+ -- signal error by clearing all
+ ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
+ end
+ ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_noaction")
+ return true
+ end
+ if qname == "example" and qtype == pdns.TXT
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_drop")
+ return true
+ end
+ if qname == "ns1.example" and qtype == pdns.A
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nxdomain")
+ return true
+ end
+ if qname == "ns1.example" and qtype == pdns.AAAA
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_nodata")
+ return true
+ end
+ if qname == "ns2.example" and qtype == pdns.A
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_appliedpolicy_kind(ref, "pdns_policy_kind_truncate")
+ return true
+ end
+
+ if qname ~= "postresolve_ffi.example" or (qtype ~= pdns.A and qtype ~= pdns.AAAA)
+ then
+ return false
+ end
+
+ local record = ffi.new("pdns_ffi_record_t")
+ local i = 0
+
+ while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, false)
+ do
+ local content = ffi.string(record.content, record.content_len)
+ local name = ffi.string(record.name)
+ if name ~= "postresolve_ffi.example"
+ then
+ return false
+ end
+ if record.type == pdns.A and content == "1.2.3.4"
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "0.1.2.3", 7, false);
+ ffi.C.pdns_postresolve_ffi_handle_add_record(ref, "add."..name, pdns.A, record.ttl, '4.5.6.7', 7, "pdns_record_place_additional", false);
+ elseif record.type == pdns.AAAA and content == "::1"
+ then
+ ffi.C.pdns_postresolve_ffi_handle_set_record(ref, i, "\\0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\10\\11\\12\\13\\14\\15", 16, true);
+ end
+ i = i + 1
+ end
+ -- loop again using raw
+ i = 0
+ while ffi.C.pdns_postresolve_ffi_handle_get_record(ref, i, record, true)
+ do
+ local content = ffi.string(record.content, record.content_len)
+ local name = ffi.string(record.name, record.name_len)
+ --pdnslog("R "..tohex(name))
+ if tohex(name) ~= "0F706F73747265736F6C76655F666669076578616D706C6500"
+ then
+ ffi.C.pdns_postresolve_ffi_handle_clear_records(ref)
+ end
+ i = i + 1
+ end
+ return true
+end
+ """
+
+ def testNOACTION(self):
+ """ postresolve_ffi: test that we can do a NOACTION for a name and type combo"""
+ query = dns.message.make_query('example', 'SOA')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 1)
+
+ def testDROP(self):
+ """ postresolve_ffi: test that we can do a DROP for a name and type combo"""
+ query = dns.message.make_query('example', 'TXT')
+ res = self.sendUDPQuery(query)
+ self.assertEqual(res, None)
+
+ def testNXDOMAIN(self):
+ """ postresolve_ffi: test that we can return a NXDOMAIN for a name and type combo"""
+ query = dns.message.make_query('ns1.example', 'A')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
+ self.assertEqual(len(res.answer), 0)
+
+ def testNODATA(self):
+ """ postresolve_ffi: test that we can return a NODATA for a name and type combo"""
+ query = dns.message.make_query('ns1.example', 'AAAA')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 0)
+
+ def testTRUNCATE(self):
+ """ postresolve_ffi: test that we can return a truncated for a name and type combo"""
+ query = dns.message.make_query('ns2.example', 'A')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 0)
+ self.assertMessageHasFlags(res, ['QR', 'TC', 'RD', 'RA'])
+
+
+ def testModifyA(self):
+ """postresolve_ffi: test that we can modify A answers"""
+ expectedAnswerRecords = [
+ dns.rrset.from_text('postresolve_ffi.example.', 60, dns.rdataclass.IN, 'A', '0.1.2.3', '1.2.3.5'),
+ ]
+ expectedAdditionalRecords = [
+ dns.rrset.from_text('add.postresolve_ffi.example.', 60, dns.rdataclass.IN, 'A', '4.5.6.7'),
+ ]
+
+ query = dns.message.make_query('postresolve_ffi.example', 'A')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 1)
+ self.assertEqual(len(res.authority), 0)
+ self.assertEqual(len(res.additional), 1)
+ self.assertEqual(res.answer, expectedAnswerRecords)
+ self.assertEqual(res.additional, expectedAdditionalRecords)
+
+ def testModifyAAAA(self):
+ """postresolve_ffi: test that we can modify AAAA answers"""
+ expectedAnswerRecords = [
+ dns.rrset.from_text('postresolve_ffi.example.', 60, dns.rdataclass.IN, 'AAAA', '1:203:405:607:809:a0b:c0d:e0f', '::2'),
+ ]
+ query = dns.message.make_query('postresolve_ffi.example', 'AAAA')
+ res = self.sendUDPQuery(query)
+ self.assertRcodeEqual(res, dns.rcode.NOERROR)
+ self.assertEqual(len(res.answer), 1)
+ self.assertEqual(len(res.authority), 0)
+ self.assertEqual(len(res.additional), 0)
+ self.assertEqual(res.answer, expectedAnswerRecords)