]> git.ipfire.org Git - thirdparty/pdns.git/blob - regression-tests.recursor-dnssec/test_PacketCache.py
Merge pull request #14024 from omoerbeek/auth-docs-modes-of-operation
[thirdparty/pdns.git] / regression-tests.recursor-dnssec / test_PacketCache.py
1 import clientsubnetoption
2 import cookiesoption
3 import dns
4 import os
5 import requests
6 import subprocess
7
8 from recursortests import RecursorTest
9
10 class PacketCacheRecursorTest(RecursorTest):
11
12 _auth_zones = {
13 '8': {'threads': 1,
14 'zones': ['ROOT']}
15 }
16
17 _confdir = 'PacketCache'
18 _wsPort = 8042
19 _wsTimeout = 2
20 _wsPassword = 'secretpassword'
21 _apiKey = 'secretapikey'
22 _config_template = """
23 packetcache-ttl=10
24 packetcache-negative-ttl=8
25 packetcache-servfail-ttl=5
26 auth-zones=example=configs/%s/example.zone
27 webserver=yes
28 webserver-port=%d
29 webserver-address=127.0.0.1
30 webserver-password=%s
31 api-key=%s
32 """ % (_confdir, _wsPort, _wsPassword, _apiKey)
33
34 @classmethod
35 def generateRecursorConfig(cls, confdir):
36 authzonepath = os.path.join(confdir, 'example.zone')
37 with open(authzonepath, 'w') as authzone:
38 authzone.write("""$ORIGIN example.
39 @ 3600 IN SOA {soa}
40 a 3600 IN A 192.0.2.42
41 b 3600 IN A 192.0.2.42
42 c 3600 IN A 192.0.2.42
43 d 3600 IN A 192.0.2.42
44 e 3600 IN A 192.0.2.42
45 f 3600 IN CNAME f ; CNAME loop: dirty trick to get a ServFail in an authzone
46 """.format(soa=cls._SOA))
47 super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir)
48
49 def checkPacketCacheMetrics(self, expectedHits, expectedMisses):
50 self.waitForTCPSocket("127.0.0.1", self._wsPort)
51 headers = {'x-api-key': self._apiKey}
52 url = 'http://127.0.0.1:' + str(self._wsPort) + '/api/v1/servers/localhost/statistics'
53 r = requests.get(url, headers=headers, timeout=self._wsTimeout)
54 self.assertTrue(r)
55 self.assertEqual(r.status_code, 200)
56 self.assertTrue(r.json())
57 content = r.json()
58 foundHits = False
59 foundMisses = True
60 for entry in content:
61 if entry['name'] == 'packetcache-hits':
62 foundHits = True
63 self.assertEqual(int(entry['value']), expectedHits)
64 elif entry['name'] == 'packetcache-misses':
65 foundMisses = True
66 self.assertEqual(int(entry['value']), expectedMisses)
67
68 self.assertTrue(foundHits)
69 self.assertTrue(foundMisses)
70
71 def testPacketCache(self):
72 self.waitForTCPSocket("127.0.0.1", self._wsPort)
73 # first query, no cookie
74 qname = 'a.example.'
75 query = dns.message.make_query(qname, 'A', want_dnssec=True)
76 expected = dns.rrset.from_text(qname, 0, dns.rdataclass.IN, 'A', '192.0.2.42')
77
78 for method in ("sendUDPQuery", "sendTCPQuery"):
79 sender = getattr(self, method)
80 res = sender(query)
81 self.assertRcodeEqual(res, dns.rcode.NOERROR)
82 self.assertRRsetInAnswer(res, expected)
83
84 self.checkPacketCacheMetrics(0, 2)
85
86 # we should get a hit over UDP this time
87 res = self.sendUDPQuery(query)
88 self.assertRcodeEqual(res, dns.rcode.NOERROR)
89 self.assertRRsetInAnswer(res, expected)
90 self.checkPacketCacheMetrics(1, 2)
91
92 # we should get a hit over TCP this time
93 res = self.sendTCPQuery(query)
94 self.assertRcodeEqual(res, dns.rcode.NOERROR)
95 self.assertRRsetInAnswer(res, expected)
96 self.checkPacketCacheMetrics(2, 2)
97
98 eco1 = cookiesoption.CookiesOption(b'deadbeef', b'deadbeef')
99 eco2 = cookiesoption.CookiesOption(b'deadc0de', b'deadc0de')
100 ecso1 = clientsubnetoption.ClientSubnetOption('192.0.2.1', 32)
101 ecso2 = clientsubnetoption.ClientSubnetOption('192.0.2.2', 32)
102
103 # we add a cookie, should not match anymore
104 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
105 res = self.sendUDPQuery(query)
106 self.assertRcodeEqual(res, dns.rcode.NOERROR)
107 self.assertRRsetInAnswer(res, expected)
108 self.checkPacketCacheMetrics(2, 3)
109
110 # same cookie, should match
111 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1])
112 res = self.sendUDPQuery(query)
113 self.assertRcodeEqual(res, dns.rcode.NOERROR)
114 self.assertRRsetInAnswer(res, expected)
115 self.checkPacketCacheMetrics(3, 3)
116
117 # different cookie, should still match
118 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2])
119 res = self.sendUDPQuery(query)
120 self.assertRcodeEqual(res, dns.rcode.NOERROR)
121 self.assertRRsetInAnswer(res, expected)
122 self.checkPacketCacheMetrics(4, 3)
123
124 # first cookie but with an ECS option, should not match
125 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso1])
126 res = self.sendUDPQuery(query)
127 self.assertRcodeEqual(res, dns.rcode.NOERROR)
128 self.assertRRsetInAnswer(res, expected)
129 self.checkPacketCacheMetrics(4, 4)
130
131 # different cookie but same ECS option, should match
132 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco2, ecso1])
133 res = self.sendUDPQuery(query)
134 self.assertRcodeEqual(res, dns.rcode.NOERROR)
135 self.assertRRsetInAnswer(res, expected)
136 self.checkPacketCacheMetrics(5, 4)
137
138 # first cookie but different ECS option, should still match (we ignore EDNS Client Subnet
139 # in the recursor's packet cache, but ECS-specific responses are not cached
140 query = dns.message.make_query(qname, 'A', want_dnssec=True, options=[eco1, ecso2])
141 res = self.sendUDPQuery(query)
142 self.assertRcodeEqual(res, dns.rcode.NOERROR)
143 self.assertRRsetInAnswer(res, expected)
144 self.checkPacketCacheMetrics(6, 4)
145
146 # NXDomain should get negative packetcache TTL (8)
147 query = dns.message.make_query('nxdomain.example.', 'A', want_dnssec=True)
148 res = self.sendUDPQuery(query)
149 self.assertRcodeEqual(res, dns.rcode.NXDOMAIN)
150 self.checkPacketCacheMetrics(6, 5)
151
152 # NoData should get negative packetcache TTL (8)
153 query = dns.message.make_query('a.example.', 'AAAA', want_dnssec=True)
154 res = self.sendUDPQuery(query)
155 self.assertRcodeEqual(res, dns.rcode.NOERROR)
156 self.checkPacketCacheMetrics(6, 6)
157
158 # ServFail should get ServFail TTL (5)
159 query = dns.message.make_query('f.example.', 'A', want_dnssec=True)
160 res = self.sendUDPQuery(query)
161 self.assertRcodeEqual(res, dns.rcode.SERVFAIL)
162 self.checkPacketCacheMetrics(6, 7)
163
164 # We peek into the cache to check TTLs and allow TTLs to be one lower than inserted since the clock might have ticked
165 rec_controlCmd = [os.environ['RECCONTROL'],
166 '--config-dir=%s' % 'configs/' + self._confdir,
167 'dump-cache', '-']
168 try:
169 ret = subprocess.check_output(rec_controlCmd, stderr=subprocess.STDOUT)
170 self.assertTrue((b"a.example. 10 A ; tag 0 udp\n" in ret) or (b"a.example. 9 A ; tag 0 udp\n" in ret))
171 self.assertTrue((b"nxdomain.example. 8 A ; tag 0 udp\n" in ret) or (b"nxdomain.example. 7 A ; tag 0 udp\n" in ret))
172 self.assertTrue((b"a.example. 8 AAAA ; tag 0 udp\n" in ret) or (b"a.example. 7 AAAA ; tag 0 udp\n" in ret))
173 self.assertTrue((b"f.example. 5 A ; tag 0 udp\n" in ret) or (b"f.example. 4 A ; tag 0 udp\n" in ret))
174
175 except subprocess.CalledProcessError as e:
176 print(e.output)
177 raise
178