]>
Commit | Line | Data |
---|---|---|
fa980c59 RG |
1 | import clientsubnetoption |
2 | import cookiesoption | |
3 | import dns | |
4 | import os | |
5 | import requests | |
628fcdb9 | 6 | import subprocess |
fa980c59 RG |
7 | |
8 | from recursortests import RecursorTest | |
9 | ||
10 | class PacketCacheRecursorTest(RecursorTest): | |
11 | ||
5b4650e2 PL |
12 | _auth_zones = { |
13 | '8': {'threads': 1, | |
14 | 'zones': ['ROOT']} | |
15 | } | |
16 | ||
fa980c59 RG |
17 | _confdir = 'PacketCache' |
18 | _wsPort = 8042 | |
19 | _wsTimeout = 2 | |
20 | _wsPassword = 'secretpassword' | |
21 | _apiKey = 'secretapikey' | |
22 | _config_template = """ | |
628fcdb9 | 23 | packetcache-ttl=10 |
91505918 | 24 | packetcache-negative-ttl=8 |
628fcdb9 | 25 | packetcache-servfail-ttl=5 |
fa980c59 RG |
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 | |
628fcdb9 | 45 | f 3600 IN CNAME f ; CNAME loop: dirty trick to get a ServFail in an authzone |
fa980c59 RG |
46 | """.format(soa=cls._SOA)) |
47 | super(PacketCacheRecursorTest, cls).generateRecursorConfig(confdir) | |
48 | ||
fa980c59 | 49 | def checkPacketCacheMetrics(self, expectedHits, expectedMisses): |
f20bacd2 | 50 | self.waitForTCPSocket("127.0.0.1", self._wsPort) |
fa980c59 RG |
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) | |
4bfebc93 | 55 | self.assertEqual(r.status_code, 200) |
fa980c59 RG |
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 | |
4bfebc93 | 63 | self.assertEqual(int(entry['value']), expectedHits) |
fa980c59 RG |
64 | elif entry['name'] == 'packetcache-misses': |
65 | foundMisses = True | |
4bfebc93 | 66 | self.assertEqual(int(entry['value']), expectedMisses) |
fa980c59 RG |
67 | |
68 | self.assertTrue(foundHits) | |
69 | self.assertTrue(foundMisses) | |
70 | ||
71 | def testPacketCache(self): | |
34e6e52f | 72 | self.waitForTCPSocket("127.0.0.1", self._wsPort) |
fa980c59 RG |
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 | ||
282b5da9 | 84 | self.checkPacketCacheMetrics(0, 2) |
fa980c59 RG |
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) | |
282b5da9 O |
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) | |
fa980c59 RG |
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) | |
282b5da9 | 108 | self.checkPacketCacheMetrics(2, 3) |
fa980c59 RG |
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) | |
282b5da9 | 115 | self.checkPacketCacheMetrics(3, 3) |
fa980c59 RG |
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) | |
282b5da9 | 122 | self.checkPacketCacheMetrics(4, 3) |
fa980c59 RG |
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) | |
282b5da9 | 129 | self.checkPacketCacheMetrics(4, 4) |
fa980c59 RG |
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) | |
282b5da9 | 136 | self.checkPacketCacheMetrics(5, 4) |
fa980c59 RG |
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) | |
282b5da9 | 144 | self.checkPacketCacheMetrics(6, 4) |
628fcdb9 | 145 | |
91505918 | 146 | # NXDomain should get negative packetcache TTL (8) |
628fcdb9 O |
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 | ||
91505918 | 152 | # NoData should get negative packetcache TTL (8) |
628fcdb9 O |
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 | ||
a4a0c6a8 | 164 | # We peek into the cache to check TTLs and allow TTLs to be one lower than inserted since the clock might have ticked |
628fcdb9 O |
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)) | |
91505918 OM |
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)) | |
628fcdb9 O |
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 |