From 18e0e4dfca3c05c78d6ff901fd4b7f4d017cbd0a Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Tue, 19 Jun 2018 12:30:57 +0200 Subject: [PATCH] rec: Add some regression tests for our Lua hooks --- .../cookiesoption.py | 74 +++++++ .../recursortests.py | 8 +- regression-tests.recursor-dnssec/test_Lua.py | 207 ++++++++++++++++++ 3 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 regression-tests.recursor-dnssec/cookiesoption.py create mode 100644 regression-tests.recursor-dnssec/test_Lua.py diff --git a/regression-tests.recursor-dnssec/cookiesoption.py b/regression-tests.recursor-dnssec/cookiesoption.py new file mode 100644 index 000000000..15ab1722f --- /dev/null +++ b/regression-tests.recursor-dnssec/cookiesoption.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2 + +import dns +import dns.edns +import dns.flags +import dns.message +import dns.query + +class CookiesOption(dns.edns.Option): + """Implementation of draft-ietf-dnsop-cookies-09. + """ + + def __init__(self, client, server): + super(CookiesOption, self).__init__(10) + + if len(client) != 8: + raise Exception('invalid client cookie length') + + if server is not None and len(server) != 0 and (len(server) < 8 or len(server) > 32): + raise Exception('invalid server cookie length') + + self.client = client + self.server = server + + def to_wire(self, file): + """Create EDNS packet as defined in draft-ietf-dnsop-cookies-09.""" + + file.write(self.client) + if self.server and len(self.server) > 0: + file.write(self.server) + + def from_wire(cls, otype, wire, current, olen): + """Read EDNS packet as defined in draft-ietf-dnsop-cookies-09. + + Returns: + An instance of CookiesOption based on the EDNS packet + """ + + data = wire[current:current + olen] + if len(data) != 8 and (len(data) < 16 or len(data) > 40): + raise Exception('Invalid EDNS Cookies option') + + client = data[:8] + if len(data) > 8: + server = data[8:] + else: + server = None + + return cls(client, server) + + from_wire = classmethod(from_wire) + + def __repr__(self): + return '%s(%s, %s)' % ( + self.__class__.__name__, + self.client, + self.server + ) + + def __eq__(self, other): + if not isinstance(other, CookiesOption): + return False + if self.client != other.client: + return False + if self.server != other.server: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + +dns.edns._type_to_class[0x000A] = CookiesOption + diff --git a/regression-tests.recursor-dnssec/recursortests.py b/regression-tests.recursor-dnssec/recursortests.py index 4641df3cf..8bfafa05d 100644 --- a/regression-tests.recursor-dnssec/recursortests.py +++ b/regression-tests.recursor-dnssec/recursortests.py @@ -42,6 +42,7 @@ disable-syslog=yes """ _config_params = [] _lua_config_file = None + _lua_dns_script_file = None _roothints = """ . 3600 IN NS ns.root. ns.root. 3600 IN A %s.8 @@ -444,10 +445,15 @@ distributor-threads=1""".format(confdir=confdir, luaconfpath = os.path.join(confdir, 'conffile.lua') with open(luaconfpath, 'w') as luaconf: if cls._root_DS: - luaconf.write("addDS('.', '%s')" % cls._root_DS) + luaconf.write("addDS('.', '%s')\n" % cls._root_DS) if cls._lua_config_file: luaconf.write(cls._lua_config_file) conf.write("lua-config-file=%s\n" % luaconfpath) + if cls._lua_dns_script_file: + luascriptpath = os.path.join(confdir, 'dnsscript.lua') + with open(luascriptpath, 'w') as luascript: + luascript.write(cls._lua_dns_script_file) + conf.write("lua-dns-script=%s\n" % luascriptpath) if cls._roothints: roothintspath = os.path.join(confdir, 'root.hints') with open(roothintspath, 'w') as roothints: diff --git a/regression-tests.recursor-dnssec/test_Lua.py b/regression-tests.recursor-dnssec/test_Lua.py new file mode 100644 index 000000000..f615cf629 --- /dev/null +++ b/regression-tests.recursor-dnssec/test_Lua.py @@ -0,0 +1,207 @@ +import clientsubnetoption +import cookiesoption +import dns +import os + +from recursortests import RecursorTest + +class GettagRecursorTest(RecursorTest): + _confdir = 'LuaGettag' + _config_template = """ + log-common-errors=yes + gettag-needs-edns-options=yes + """ + _lua_dns_script_file = """ + function gettag(remote, ednssubnet, localip, qname, qtype, ednsoptions, tcp) + + local tags = {} + local data = {} + + -- make sure we can pass data around to the other hooks + data['canary'] = 'from-gettag' + + -- test that the remote addr is valid + if remote:toString() ~= '127.0.0.1' then + pdnslog("invalid remote") + table.insert(tags, 'invalid remote '..remote:toString()) + return 1, tags, data + end + + -- test that the local addr is valid + if localip:toString() ~= '127.0.0.1' then + pdnslog("invalid local") + table.insert(tags, 'invalid local '..localip:toString()) + return 1, tags, data + end + + if not ednssubnet:empty() then + table.insert(tags, 'edns-subnet-'..ednssubnet:toString()) + end + + for k,v in pairs(ednsoptions) do + table.insert(tags, 'ednsoption-'..k..'-count-'..v:count()) + local len = 0 + local values = v:getValues() + for j,l in pairs(values) do + len = len + l:len() + + -- check that the old interface (before 4.2.0) still works + if j == 0 then + if l:len() ~= v.size then + table.insert(tags, 'size obtained via the old edns option interface does not match') + end + value = v:getContent() + if value ~= l then + table.insert(tags, 'content obtained via the old edns option interface does not match') + end + end + end + table.insert(tags, 'ednsoption-'..k..'-total-len-'..len) + end + + if tcp then + table.insert(tags, 'gettag-tcp') + end + + -- test that tags are passed to other hooks + table.insert(tags, qname:toString()) + table.insert(tags, 'gettag-qtype-'..qtype) + + return 0, tags, data + end + + function preresolve(dq) + + -- test that we are getting the tags set by gettag() + -- and also getting the correct qname + local found = false + for _, tag in pairs(dq:getPolicyTags()) do + if dq.qname:equal(tag) then + found = true + end + dq:addAnswer(pdns.TXT, '"'..tag..'"') + end + + if not found then + pdnslog("not valid tag found") + dq.rcode = pdns.REFUSED + return true + end + + if dq.data['canary'] ~= 'from-gettag' then + pdnslog("did not get any data from gettag") + 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 + dq:addAnswer(pdns.AAAA, '2001:db8::1') + end + + return true + end + """ + + @classmethod + def setUpClass(cls): + + cls.setUpSockets() + confdir = os.path.join('configs', cls._confdir) + cls.createConfigDir(confdir) + cls.generateRecursorConfig(confdir) + cls.startRecursor(confdir, cls._recursorPort) + + @classmethod + def tearDownClass(cls): + cls.tearDownRecursor() + + def assertResponseMatches(self, query, expectedRRs, response): + expectedResponse = dns.message.make_response(query) + + if query.flags & dns.flags.RD: + expectedResponse.flags |= dns.flags.RA + if query.flags & dns.flags.CD: + expectedResponse.flags |= dns.flags.CD + + expectedResponse.answer = expectedRRs + print(expectedResponse) + print(response) + self.assertEquals(response, expectedResponse) + + def testA(self): + name = 'gettag.lua.' + expected = [ + dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'), + dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1']) + ] + query = dns.message.make_query(name, 'A', want_dnssec=True) + query.flags |= dns.flags.CD + res = self.sendUDPQuery(query) + self.assertResponseMatches(query, expected, res) + + def testTCPA(self): + name = 'gettag-tcpa.lua.' + expected = [ + dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'), + dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-1', 'gettag-tcp']) + ] + query = dns.message.make_query(name, 'A', want_dnssec=True) + query.flags |= dns.flags.CD + res = self.sendTCPQuery(query) + self.assertResponseMatches(query, expected, res) + + def testAAAA(self): + name = 'gettag-aaaa.lua.' + expected = [ + dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'AAAA', '2001:db8::1'), + dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [ name, 'gettag-qtype-28']) + ] + query = dns.message.make_query(name, 'AAAA', want_dnssec=True) + query.flags |= dns.flags.CD + res = self.sendUDPQuery(query) + self.assertResponseMatches(query, expected, res) + + def testSubnet(self): + name = 'gettag-subnet.lua.' + subnet = '192.0.2.255' + subnetMask = 32 + ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask) + expected = [ + dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'), + dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask), + 'ednsoption-8-count-1', 'ednsoption-8-total-len-8']), + ] + query = dns.message.make_query(name, 'A', want_dnssec=True, options=[ecso]) + query.flags |= dns.flags.CD + res = self.sendUDPQuery(query) + self.assertResponseMatches(query, expected, res) + + def testEDNSOptions(self): + name = 'gettag-ednsoptions.lua.' + subnet = '192.0.2.255' + subnetMask = 32 + ecso = clientsubnetoption.ClientSubnetOption(subnet, subnetMask) + eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef') + eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de') + + expected = [ + dns.rrset.from_text(name, 0, dns.rdataclass.IN, 'A', '192.0.2.1'), + dns.rrset.from_text_list(name, 0, dns.rdataclass.IN, 'TXT', [name, 'gettag-qtype-1', 'edns-subnet-' + subnet + '/' + str(subnetMask), + 'ednsoption-10-count-2', 'ednsoption-10-total-len-32', + 'ednsoption-8-count-1', 'ednsoption-8-total-len-8' + ]), + ] + query = dns.message.make_query(name, 'A', want_dnssec=True, options=[eco1,ecso,eco2]) + query.flags |= dns.flags.CD + res = self.sendUDPQuery(query) + self.assertResponseMatches(query, expected, res) + +# TODO: +# - postresolve +# - preoutquery +# - ipfilter +# - prerpz +# - nxdomain +# - nodata -- 2.40.0